iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 21
0
Modern Web

認真學前端開發 - 以 TodoList 為例系列 第 21

Day21 - 使用 Firebase 來做儲存 [Part3][Promise 篇]

昨天練習寫了 firebasedatabase function 但要整進 TodoApp 必須要寫很多 callback 來處理

當 ref 取得完資料的時候會把結果用 callback [回乎] 的方式拿回來

像是

callback = (snapshot) => {
  // handle snapshot
}
listRef.once('value', callback)

但是用一堆 callback 的方式會寫出像是波動拳的程式碼

doFirstAsync((data1) => {
  doSecondAsync((data2) => {
    doThirdAsync((data3) => {
      doForthAsync((data4) => {
        doMore((data5) => {
          // 猴溜ken
        }, failureCb)
      }, failureCb)
    }, failureCb)
  }, failureCb)
}, failureCb)

Promise

為了避免寫出像 波動拳 的或稱 callback hell 好像又叫做 Pyramid of Doom 悲慘金字塔?

Promise 就誕生了

官方說 Promise 有三大保證

不如舊做法,一個 Promise 有這些保證:

  • Callback 不會在當次的迴圈運行結束前呼叫。
  • Callback 用 .then 添加,在非同步運算結束後呼叫,像前面那樣。
  • 複 Callback 可以透過重複呼叫 .then 達成。
    但 Promise 主要的立即好處是串連。

如果有興趣可以讀讀看官方怎麼寫的我覺得寫得非常好

https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Using_promises#%E4%B8%B2%E9%80%A3

練習寫看看

今天有 3 個 function 要依序被呼叫就可以用 promise 這樣寫

const firstPromise = () => new Promise((resolve) => {
  setTimeout(() => {
    console.log('first promise completed');
    resolve();
  }, 300);
});

const secondPromise = () => new Promise((resolve) => {
  setTimeout(() => {
    console.log('second promise completed');
    resolve();
  }, 200);
});

const thirdPromise = () => new Promise((resolve) => {
  setTimeout(() => {
    console.log('3rd promise completed');
    resolve();
  }, 100);
});

firstPromise()
  .then(secondPromise)
  .then(thirdPromise)
  .then(() => {
    console.log('done');
  });

而像這樣參數一致的 Promise function 官方很熱心地教我們可以這樣寫

[firstPromise, secondPromise, thirdPromise, () => { console.log('done'); }].reduce((p, f) => p.then(f), Promise.resolve());

這兩個都會依序輸出

first promise completed
second promise completed
3rd promise completed
done

也可以使用 組合 串連的方式,讓 function 可以被複用的 compose 寫法

let applyAsync = (acc,val) => acc.then(val);
let composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));

const applyAsync = (acc, val) => acc.then(val);
const composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));

const composed = composeAsync(firstPromise, secondPromise, thirdPromise);
composed().then(() => {
  console.log('done');
});

Firebase 的使用

基本上 delete 和 update 會回傳 promise

Receive a Promise
To know when your data is committed to the Firebase Realtime Database server, you can use a Promise. Both set() and update() can return a Promise you can use to know when the write is committed to the database.

list 就必須要用 callback 了,為了方便使用,將其改為回傳 Promise

const getList = () => {
  const listRef = firebase.database().ref(KEY_TODOS).orderByChild('ts');
  return new Promise((resolve, reject) => {
    listRef.once('value', (snapshot) => {
      const res = [];
      snapshot.forEach((childSnapshot) => {
        const childKey = childSnapshot.key;
        const childData = childSnapshot.val();
        res.push({
          id: childKey,
          ...childData,
        });
      });
      resolve(res);
    });
  });
};

現在來試試做一序列的操作吧

  1. 看有多少資料
  2. 新增一筆資料
  3. 更新該筆資料
  4. 刪除該資料

寫法如下:

getList().then((list) => {
  console.log(list);
  return writeUserData('測試 promise 資料', false);
})
  .then(getList)
  .then((list) => {
    const lastItem = list[list.length - 1];
    console.log('========');
    return updateTodo(lastItem.id, {
      ...lastItem,
      checked: true,
    });
  })
  .then(() => {
    console.log('updated');
    return getList();
  })
  .then((list) => {
    console.log('========');
    console.log(list);
    const lastItem = list[list.length - 1];
    console.log('========');
    console.log(`刪除${lastItem.id}`);
    return deleteTodo(lastItem.id);
  })
  .then(getList)
  .then((list) => {
    console.log('========');
    console.log(list);
  });

成功的結果如下

取得列表
[ { id: '-LPoJS_3YqW7LkjAJMSF',
    checked: false,
    name: '第一個任務',
    ts: 1540623554884 },
  { id: '-LPoJTDfsMGLsJYBrld2',
    checked: false,
    name: '第二個任務',
    ts: 1540623557547 } ]

新增一筆資料:
[ { id: '-LPoJS_3YqW7LkjAJMSF',
    checked: false,
    name: '第一個任務',
    ts: 1540623554884 },
  { id: '-LPoJTDfsMGLsJYBrld2',
    checked: false,
    name: '第二個任務',
    ts: 1540623557547 },
  { id: '-LQ-8l6nBV_SFJh4seaQ',
    checked: false,
    name: '測試 promise 資料',
    ts: 1540822078020 } ]
========
更新剛新增的資料
========
[ { id: '-LPoJS_3YqW7LkjAJMSF',
    checked: false,
    name: '第一個任務',
    ts: 1540623554884 },
  { id: '-LPoJTDfsMGLsJYBrld2',
    checked: false,
    name: '第二個任務',
    ts: 1540623557547 },
  { id: '-LQ-8l6nBV_SFJh4seaQ',
    checked: true,
    name: '測試 promise 資料',
    ts: 1540822078020 } ]
========
刪除-LQ-8l6nBV_SFJh4seaQ
========
[ { id: '-LPoJS_3YqW7LkjAJMSF',
    checked: false,
    name: '第一個任務',
    ts: 1540623554884 },
  { id: '-LPoJTDfsMGLsJYBrld2',
    checked: false,
    name: '第二個任務',
    ts: 1540623557547 } ]

明天就來將這個 firebase service 改成 library 來讓 TodoList 使用吧!

  • github
  • 執行 babel-node src/service/promise_pratice.js

上一篇
Day20 - 使用 Firebase 來做儲存 [Part2]
下一篇
Day22 - 使用 Firebase 來做儲存 [Part4][完成]
系列文
認真學前端開發 - 以 TodoList 為例30

尚未有邦友留言

立即登入留言