第一次看到promise的實戰, 是出現在口罩地圖的示範碼, 當時老師透過promise載入兩個ajax. 自己之前也有寫了一篇文章在講相關的概念, 但是覺得還是沒有掌握好.
知道什麼時候要用是一回事, 但是怎麼用又是一回事, 還有就是為什麼要用? 所以寫了這篇文章, 希望能徹底了解Promise, async, await.
(此篇文章是以筆者的理解加以撰寫,所以基礎觀念很多都跳過, 不適合基礎者閱讀)
JS的運作原理
JS本身是單執行緒, 也就是一次只能處理一件事的語言, 但程式要處理的事情很多, 單執行緒不太夠用啊, 怎麼辦?
那就要給優先順序
JS機制是會把非同步事件移動到最後面執行
哪些是非同步事件呢?
名詞解釋
簡單了解了JS運作原理, 不免俗的先來個名詞解釋.
Ajax
是一個技術名稱 ⇒ 取得遠端資料的非同步行為
為什麼是非同步?
問這問題先假設如果是同步, 而遠端的資料又多又雜,又要先處理的話, 那整個程式都卡在那
以最近最夯的買酒精為例:
原本要結帳的沒辦法結帳, 如果先等排隊買酒精的人都結帳完, 才處理後面的結帳, 那這樣是一件沒有效率的事情.
那怎麼辦?
先處理結帳的客戶, 買酒精的人繼續排隊. 等到後面結帳的人都沒人了, 再來處理酒精的結帳.
Promise
是一個語法 ⇒ 處理非同步行為
為什麼要用 ⇒ 因為要有一個標準化的介面
白話一點: 透過Promise管理ajax行為, Promise是用來改善 JavaScript 非同步的語法。
再白話一點: 我要怎麼管理排隊的人潮拉?
Promise 與 Async、Await 有什麼關係?
Async、Await 可以基於 Promise 讓非同步的語法的結構類似於 “同步語言”,更易讀且好管理。
白話一點: 優化promise的孿生兄弟
再白話一點: 比原本Promise方法更好的作法
Promise 的結構及狀態
原生調用
那如果console.dir(Promise) 會出現什麼呢?
Promise 本身是一個建構函式,函式也是屬於物件的一種,因此可以附加其它屬性方法在上,透過 console 的結果可以看到 Promise 可以直接使用 all、race、resolve、reject 的方法
- Promise.all
- Promise.race
- Promise.resolve
- Promise.reject
(若想了解用法, 可以直接參考段落Promise方法)
Promise 建構函式的同時,必須傳入一組函式作為參數,此函式的參數包含resolve
, reject
,這兩個方法分別代表成功與失敗的回傳結果,特別注意這兩個僅能回傳其中之一,且必定只能回傳一次。回傳後表示此 Promise 事件結束
建構函式 new 調用
先來看兩種調用的方法
1 | // 在函數裡面的寫法 |
那如果console.dir(p) 會出現什麼呢?
Promise 建構函式 new 出的物件,則可以使用其中的原型方法, (在 prototype 內,其中就包含 then、catch、finally,這些方法則必須在新產生的物件下才能呼叫。
- p.then() => Promise 回傳正確
- p.catch() => Promise 回傳失敗
- p.finally() => 非同步執行完畢
狀態
在 Promise 的執行函式中,可以看到以下兩個屬性:
- [[PromiseStatus]]:
"pending"
-> 表示目前的進度狀態 - [[PromiseValue]]:
undefined
-> 表示resolve
或reject
回傳的值
Promise 中會使用 resolve 或 reject 回傳結果,並在調用時使用 then 或 catch 取得值。
更改狀態看看
1 | const promise2 = () => { |
then vs catch 舉例
上列的三種狀態每次執行必定會經過 Pending,接下來進入 Fulfilled 或 Rejected 的其中之一,並且可以使用 then() 及 catch() 取得成功或失敗的結果。
1 | let a = new Promise(function (resolve, reject) { |
Promise 另一個特點在於 then、catch 都可以使用鏈接的方式不斷的進行下一個任務.
實例來一下
筆者這邊有找到一個很好的範例來解釋
- 為什麼要用?
- 怎麼用? 知道有這個武器是理所當然, 但是什麼時候要把這武器叫出來?
首先我要可以印出A ⇒ B ⇒ C的順序
無法確定callback回傳的順序
1 | function logWord(word){ |
改成callback
1 | function logWord(word, callback) { |
改成Promise
簡單來說就是在非同步事件上在外面包裝一層
1 | function logWord(word){ |
改成Async & Await 寫法
1 | function logWord(word){ |
Promise 方法
展開後可以看到以下方法:
- all -> 多個 Promise 行為同時執行,全部完成後統一回傳。
- race -> 多個 Promise 同時執行,但僅回傳第一個完成的。
- Promise.reject, Promise.resolve -> 定義 Fulfilled 或 Rejected 的 Promise 物件。
這邊先定義一個函式, 為後續例子所用
1 | function promise(num, time=500){ |
Promise.all
鳴槍起跑, 全部一起跑, 比賽什麼時候結束? 全部的馬跑完才會結束
透過陣列的形式傳入多個 promise 函式,在全部執行完成後回傳陣列結果
這個方法很適合用在多支 API 要一起執行,並確保全部完成後才進行其他工作時。
1 | // 這是針對上一part的 resolve, reject的函式所編寫 |
Promise.race
透過陣列的形式傳入多個 promise 函式,在全部執行完成後回傳單一結果,結果為第一個運行完成的,以下範例來說就會回傳 promise(1) 的結果
1 | Promise.all([promise(1), promise(2), promise(3, 3000)]) |
適合用在完整性, 而不是順序性, 小案子適合
Promise.reject, Promise.resolve
這兩個方法是直接定義 Promise 物件已經完成的狀態(resolve, reject),與 new Promise 一樣會產生一個新的 Promise 物件,但其結果是已經確定的,以下提供範例說明:
1 | // 使用 Promise.resolve 產生一個新的 Promise 物件,此物件可以使用 then 取得 resolve 的結果。 |
簡單來說, 如果直接console.dir(result)
, 會得到如下的圖片
在[[PromiseStatus]]裡面, 沒有上述所說的三種狀態, 且連值也都被定義好了
Await and Async
await: 等待
async: 非同步
Async
async 本身也是類似 Promise,在正確執行的情況下 return 會傳回 resolved 的狀態,也可以使用 then 來接收正確的資料。
底下例子可以窺知一二
1 | // 一般來說Promise的寫法 |
把run()
和run2()
, console出來, 結果一樣, 所以async
根本就是Promise
的語法糖
可以看出, return回來的東西就是resolveValue
但其實這樣還不夠甜, 甜是甜在await
Await
Await 顧名思義就是等待,在這個 Promise 結束前後面的程式碼都無法被執行。
1 | function run(){ |
錯誤
出現錯誤了怎麼辦? 用try, catch去接
1 |
|
補充
1 | function promiseFn(num){ |
Fetch建立多個Ajax連續處理
1 | fetch('https://reqres.in/api/unknown/2') |