0%

Promise - 如何運用 Promise 處理非同步事件

文章目的

只要是前端工程師一定會碰到需要用 JS 來處理非同步行為,此篇文章用來記錄使用 ES6 的 Promise 來處理非同步事件。

非同步行為

這邊我先來簡單的介紹一下何謂非同步行為,我們都知道 JS 是單一執行緒,他會根據程式碼的行數,一行一行的從小到大運作,來舉個例:

1
2
3
console.log('第一行')
console.log('第二行')
console.log('第三行')

如果想要看到印出 第三行 勢必會先看到 第一行第二行 出現在 console 中,因為要印出 第三行 一定得等前兩行運行完才會輪到它。

我們都知道 JS 在 call api 時候都會需要發送請求給伺服器,伺服器再根據請求回傳結果給我們,發送跟回傳都會需要時間,而且還會受到很多因素影響需要的時間,例如:網速、伺服器狀況等。

因此我們在 call api 時候就會採取非同步行為,非同步行為可以幫助我們 JS 在執行時,不會因為執行到 call api 時為了等待發送跟回傳,而停在那裡,導致後面程式碼無法執行的情況發生。

當然除了 call api 的 AJAX 是非同步行為外,我們常看到的 setTimeoutaddEventListener 其實都算是非同步行為,非同步行為都會被放到事件佇列 (Event Queue) 中,在同步行為完成後或是被觸發後執行。

Promise 優點

那為什麼會推薦利用 Promise 來處理非同步行為呢?
因為非同步行為,會有些無法忽視的常見缺點,透過 Promise 可以幫助我們解決這些缺點,缺點如下:

  1. 回呼地獄 ( call back hell )
  2. 寫法不一致
  3. 無法同時執行

來看看 Promise 是怎麼解決這些缺點的。

回呼地獄

相信有接觸 JS 的開發者們對 回呼地獄call back hell波動拳 這些名詞不陌生,來看看下面這張圖,你一定更能體會。


圖片來源

基本上會有回呼地獄就是因為當非同步行為一多,我們又希望能有順序性的執行非同步行為,那非 Promise 的寫法就會如同上圖一樣,在一個非同步行為完成後,把下一個非同步行為包覆在前一個的 call back 中,導致結構越來越巢狀,因為輪廓很像波動拳,所以才又有波動拳的說法。

那 Promise 又是如何解決這個問題呢?
我們以 Promise 的 api 套件 axios 做例子,來看一下程式碼:

1
2
3
4
5
6
7
8
axios.get(url)
.then((res) => {
console.log('first:', res)
return axios.get(url2)
})
.then((res2) => {
console.log('second:', res2)
})

Promise 利用 return 來執行第二次的行為,並且將結果透過 then 傳入,這樣是不是看起來就比較舒服,解決了回呼地獄的巢狀結構。

寫法不一致

寫法不一致的行為我們以 jQuery 為例,jQuery 在執行 AJAX 的時候就會有 done、success,來接收我們的執行結果。
Promise 都是一律使用 then 來做接收,且不會因為不同的套件產生不同的寫法,因此解決了寫法不一致的問題。

無法同時執行

非同步有個特性在於我們無法得知何時開始、何時結束,若我們需要執行的某個行為是需要多個非同步執行完後才做執行,那非 Promise 的寫法或套件大部分很難做到這點。

Promise 可以透過 Promise.all 來解決這個問題:

1
2
3
4
5
6
Promise.all([
axios.get(url),
axios.get(url2)
]).then(([res, res2]) => {
console.log('first:', res, 'second:', res2)
})

Promise.all 中透過陣列傳入多個非同步行為,再用 then 跟陣列接收多個結果,這樣子就很輕易地解決了需要同時執行的需求。

結語

這篇文章簡單的介紹了一下 Promise 在非同步行為上的處理以及優點。

後續希望有時間能把 Promise 寫成一個小系列文章做個完整介紹。

若有更新系列文章我會再嘗試補上文章連結。

系列連結

Promise - Promise 運作概念
Promise - Promise Chain
Promise - Promise 使用方法


參考資料

六角學院-JavaScript核心篇