JavaScript 中的 Promise 是什麼?以及為什麼你要懂 Promise
前言
那麼這一篇會來談談什麼是 Promise 以及 Async/Await 語法。
Promise 概念
Promise 其實就是承諾的意思,當然不是指 Promise 中文翻譯是承諾這件事,如果有這麼簡單我也不用特別寫成一篇文章。
那麼先來講講為什麼你要懂 Promise 這件事情。
你在開發時應該很常遇到所謂的「非同步」事件,例如以下程式碼預期應該是要依照順序跑:
1 | console.log('Start'); |
但實際上結果卻是…
1 | 1. Start |
那這件事情與 Promise 有什麼關聯性呢?實際開發來講,我們往往會預期希望結果是依照我們要的順序去運作,畢竟程式碼亂跳的話結果也一定會不同。
那 Promise 概念是什麼呢?雖然中文翻譯是一個承諾的意思,但實際上是如何呢?我們試著用比較現實生活層面來舉例。
你今天去人超多的百貨公司買一點小吃,例如…繼光香香雞
當你點好了繼光香香雞後店員給你了一個取餐單或者是取餐叫號器
等你拿到那一張取餐單 or 叫號器之後,你就會等到它叫你去取餐。
當然這段時間你依然可以去做點其他事情,只是你等一下會回頭處理他的事情,只是你一定會是先去繼光香香雞點餐後再去取餐。
所以你可以理解到一件事情 Promise 簡單來講就是我答應你我等一下一定會做什麼事情。
那 Promise 該如何撰寫呢?讓我們繼續往下看。
Promise
Promise 本身是一個建構函式,因此會需要使用到 new
語法來實例化它,而它主要會帶入兩個參數,分別是
resolve
- 成功reject
- 失敗
1 | new Promise(function (resolve, reject) {}); |
而宣告方式有兩種
1 | // 第一種 |
而這兩者寫法並沒有太大差異,所以這邊主要看你自己需求來調整撰寫。
Promise 使用
而 Promise 的使用方式也相當簡單,當你宣告了一個 Promise
後,你可以使用 then
與 catch
串接 Promise
回傳的結果
1 | const myName = function() { |
那麼問題來了,什麼時候會跑 then
什麼時候會跑 catch
呢?其實這是我們可以決定的,前面我們有講到實例化 Promise
時會帶入兩個參數,分別是 resolve
與 reject
,而當若結果回傳的是 resolve
則會跑 then
,反之若是 reject
就是 catch
。
Promise 靜態方法
除此之外 Promise 也有提供一些靜態方法(不需要實例化即可使用的方法):
Promise.all(iterable)
Promise.race(iterable)
Promise.reject(resason)
Promise.resolve(value)
Promise.all
當你有多個 Promise
事件需要一起執行與完成時,就可以使用 Promise.all
1 | const myName1 = function() { |
這邊需要注意回傳的結果會與你一開始傳入的順序是相同的,而這個方法很適合用於打多隻 API 時。
Promise.race
Promise.race
簡單來講就是,當然當有任一個事件完成時,就會只回傳那一個事件,舉例來講 myName2
將會比 myName1
更快結束:
1 | const myName1 = function() { |
Promise.reject & Promise.resolve
最後是關於 Promise.reject
與 Promise.resolve
的部分,這兩個方法就是只取得成功或是失敗得結果而已
1 | const myName = function() { |
反之若改成 Promise.reject
則必定回傳失敗結果。
其他方法
最後其實還有兩個方法
Promise.any
Promise.allSettled
Promise.any
只要有任何一個成功,就必定會執行:
1 | const myName1 = function() { |
最後 Promise.allSettled
只要傳入的 Promise
事件都完成或者失敗後就會回傳結果與狀態
1 | const myName1 = function() { |
Promise.allSettled
很適合用於確定所有 Promise
的執行狀態。
Promise 狀態
那麼 Promise 本身也有三種狀態
- pending(擱置)
- fulfilled(完成、實現)
- rejected(拒絕)
如果你有針對前面的程式碼稍微嘗試運行過的話,基本上你都會看到上面任一種的提示訊息而這也是 Promise 的狀態。
Promise.prototype.then 特別之處
其實 Promise.prototype.then
是一個很特別的方法,為什麼這樣說呢?在前面範例中,我們知道 then
代表著 Promise
成功,而 catch
則是失敗,因此一個 Promise
就會寫成這樣:
1 | const myName = function() { |
但是 Promise.prototype.then
是可以寫成這樣的:
1 | const myName = function() { |
而這兩者都是屬於被允許的範圍內,但通常來講我們還是會採用前者的方式 then
& catch
,畢竟錯誤訊息與成功訊息都放在同一個 then
內是格外的難閱讀,更不用說當邏輯越來越複雜時。
另外 then
是可以串接的,因此如果希望依照順序執行的話,除了使用 Promise.all
之外也可以透過 return
持續將結果往下一個 then
傳遞:
1 | const myName1 = function() { |
ES7 Async/Await
那麼前面的 Promise
語法幫助了我們遠離早期開發的 Callback Hell 問題
讓我們可以從 Callback Hell 轉換為串聯的方式,但是實際開發來講,還是有可能會讓 Callback Hell 問題發生,因此 ES7 出現了一個新的語法,也就是 Async/Await,可以刊稱真正的 Callback Hell 救贖之光。
為什麼這樣說呢?我們都知道一個 Promise
都必須搭配 then
串接成功結果:
1 | const myName = function() { |
而 ES7 Async/Await 出現之後我們可以大幅的減少巢狀結構,只需要這樣寫即可
1 | const myName = function() { |
早期瀏覽器是不能直接如上方這樣寫的,因為 await
語法必須包在一個函式內:
1 | const myName = function() { |
是後來瀏覽器才允許你可以直接在頂層使用 await
。
Async
那麼 async
語法有什麼特別的呢?簡單來講 async function
可以將原本的函式轉換成 Promise
:
1 | async function fn() { |
這邊要注意一件事情,async
等同使用 Promise.resolve
:
1 | async function fn() { |
其實是被隱含轉換成了以下:
1 | function fn() { |
Await
那麼 await
呢?其實 await
概念等同 then
:
1 | async function fn() { |
底層轉換成了 then
的概念:
1 | function fn() { |
async/await 特性
那麼看完前面的一些介紹之後,其實你就可以知道 async
/await
可以更是大幅的優化 Promise
寫法,因此原本的 Promise
寫法可以如下:
1 | const myName = function() { |
但是透過 async
/await
特性之後,你可以改寫成以下:
1 | const myName = async () => 'Ray'; |
甚至是這種極致的寫法:
1 | const myName = async () => 'Ray'; |
try/catch
那麼最後是關於 async
/await
常見的狀況,因為使用 async
/await
語法後我們就無法取得錯誤訊息,因此若要捕抓錯誤訊息的話,就必須使用 try/catch
,而使用的方式非常簡單
1 | const myName = async () => 'Ray'; |
透過這樣的方式,就可以補抓一些錯誤訊息了。
實戰相關
那麼在實戰上我們很常使用 axios
or fetch
等 API 獲取遠端的資料,而這兩者也都是使用 Promise
製作的,因此你不但可以使用 then
串鏈之外,也可以使用 async
/await
撰寫,這邊就不再多次說明了,就當作留給閱讀的人一個功課,可以試著去回顧自己之前寫的 axios
與 fetch
語法讓它更簡短更優化吧。