善用Promise特性優化非同步
為什麼要Promise
JS是單執行緒的程式語言,透過callback的方式實現出非同步的操作。
日常生活中,我們時常都在Promise,我Promise你這件事會做到,完成或失敗就執行指定的任務。
Promise基本用法
假設有一個實際情境,做完任務再拿獎金
1 | doTask(); // type: Normal Function |
但是doTask需要時間呀,獎金也是要任務完成才能拿,如果接任務和拿獎金一起做,似乎不太好。
因此doTask不應該只是一個Function,它應該要是一個Promise。
而且做完任務才能執行以下的任務。
1 | doTask() // type: Promise |
一定要確認doTask回傳為Promise型態才可以使用非同步,否則會噴錯
錯誤的做法:
1 | doTask() // type: Normal Function |
也可以使用ES6+的方法使用async await改變開發的閱讀體驗:
1 | async function() { // make sure this is async function |
Promise的error handling
可是doTask有成功也有失敗呀?總不能失敗了也要拿錢給他吧?
callback寫法
可以調整一下寫法:
1 | doTask() |
doTask會回傳結果為response,then將收到的結果判斷成功還是失敗
成功才能拿獎金,失敗則回傳失敗的原因做其他例外處理
ES6+寫法
也可以搭配使用async await的寫法,避免後續維護寫出callback hell的程式
1 | try { |
Promise進階用法
前面介紹Promise的基本觀念之後,再來就可以做更進一步的操作了
不過有些Promise事件並不需要過度去依賴非同步來達成結果
今天如果一個頁面需要進行多個動作時,你會怎麼做呢?
或是換句話說,今天你去外面點餐,你會怎麼點?
狀況題
你的任務如下:
1 | goOutside(); // Normal Function |
今天中午肚子餓,走到餐廳幫家裡的人買東西,你要吃漢堡,你哥要吃潛艇堡,你姐要吃喝飲料,這時候你點餐的時候你會怎麼點?
(假設店員數量充足不忙碌的情況下)
提示:直接跑是不可行的,因為點餐都是非同步,所以直接執行就會goOutside再goHome,不符合預期情境。
迷:我知道,都加上await對吧?
1 | goOutside(); // Normal Function |
看起來似乎可行,因為點完餐再回家,餐點都帶回家了,合理合理。
可是這個執行模式似乎有一點瑕疵,你看出來了嗎?
如果再翻譯成callback的寫法,你大概就比較看得出來了:
1 | goOutside(); // Function |
翻譯故事
1 | 你:我要一份漢堡 |
你聽完之後有什麼感想?為什麼不一次點完餐比較快?難道是要賺三張抽獎券嗎(X
所以應該變成這樣才對:
1 | 你:我要一份漢堡,一份潛艇堡,一份飲料 |
簡潔多了~那麼我們再翻譯回程式吧,可以怎麼做呢?
Promise.all
Promise.all也會回傳一個Promise,裡面所有的Promise完成成功之後就會回傳被resolve的Promise。
因此我們可以把剛剛要一起執行的任務寫在一起。
1 | goOutside(); // Normal Function |
這樣就不用浪費過多的時間了,讓我們再來修飾一下:
回傳的結果也會是一個array,包含所有Promise的結果
1 | goOutside(); // Normal Function |
不過Promise.all有一個小問題需要注意
請看mozilla文件說明:
當任一個陣列成員被拒絕則 Promise.all 被拒絕。例如,若傳入四個將在一段時間後被解決的 promises,而其中一個立刻被拒絕,則 Promise.all 將立刻被拒絕。
重點:Promise.all具有快速回傳失敗的特性,只要發生問題就會中斷
因此只要有一個商品缺貨不能做,那我就無法把所有商品帶回家了
另外在error handling也不太好追蹤,假設有多個Promise可能有問題,他只會回傳一個:
1 | goOutside(); // Normal Function |
可是我就算缺貨也要帶回家給想吃的人呀?那我們可以怎麼做呢
Promise.allSettled
ES2020之後,你可以使用Promise.allSettled加強你的error handling體驗
總是會回傳所有Promise成功與失敗的結果,讓你更好撰寫邏輯
1 | goOutside(); // Normal Function |
該用all還是allSettled
你需要考慮的部分大概有:
- 瀏覽器相容性
- 目前大概舊版的Edge會比較有問題之外,大部分是可以支援的,可以參考看看caniuse
- 比較需求
- all:當其中一個Promise rejected馬上結束
- allSettled:絕對不會被rejected,因此邏輯可以直接寫在then或是不用寫try catch,需另外解析array內的resolved與rejected。
進階補充
以下是補充額外的資訊,可以斟酌看一下
進一步認識Promise
上面為了簡單解釋,因此審略了一部份東西補充到這裡
Promise本身其實是有狀態的:
- pending:初始狀態
- 尚未被fulfilled或rejected
- fulfilled:成功完成
- onFulfilled/resolve
- rejected:操作失敗
- onRejected/reject
如何自己實作Promise all
Promise all自己本身會回傳Promise,所以在裡面可以直接寫return Promise
再分別解決每一個Promise,直到所有的Promise resolved
1 | // Promise.all |
參考資料
Promise.allSettled() VS Promise.all() in JavaScript
What is a Promise