JavaScript 有些程式是非同步的(例如 ajax, setTimeout, setInterval),為了確保這些非同步程式能照我們想要的順序執行,我們使用了回呼函式 (以下稱 callback function) 來達成我們的需求,但 callback function 有個問題是只要程序一多,程式碼會變得非常難以閱讀,於是 ES6 提出了一個解決方案:Promise。
甚麼是 Promise?
首先要知道,Promise 是一個物件(object),它代表一個即將完成、或失敗的非同步操作,以及它所產生的值。一個 Promise 物件透過 new 及其建構式建立。這個建構式接收一個叫作”執行器函式(executor function)”的引數。此函式裡面有兩個 callback function 作為引數,分別是 resolve 與 reject。
1 | new Promise( /* executor */ function(resolve, reject) { ... } ); |
resolve 與 reject 函數的作用
任何一個 Promise 物件在剛被建立時,一定都是處於 pending 狀態,只有執行完裡面的非同步程式後,才能確認該 Promise 是成功或失敗。
如果該 Promise 的非同步程式成功完成時,將會執行第一個函式 resolve 之結果值,相對的,若非同步程式失敗時,Promise 將會執行第二個函式 reject 之結果值。簡單的說,即是把這兩個 callback function 當作該 Promise 成功或失敗的接口。
成功便調用第一個回呼函數 resolve,將參數傳遞出去。
失敗便調用第二個回呼函數 reject,將參數傳遞出去。
1 | const myPromise = new Promise(function(resolve, reject){ |
resolve 與 reject 可任意命名
Executor 裡面的 callback function(resolve 與 reject)可用其他名稱代替。
1 | const myPromise = new Promise(function(successed, failed){ |
第二個 reject 函式不一定要放
resolve 或 reject 函式不必兩個都放,如果你確定你的 Promise 裡面的程式一定會執行成功,可以只放第一個 callback function,也就是 resolve。如果你的 Promise 可能有失敗結果,就不能偷懶,一定要放兩個 callback function,第一個 callback function 是成功時執行,第二個 callback function 是失敗時執行。
1 | const myPromise = new Promise(function(reject){ |
上面有個小小的陷阱,在 executor 裡面我只放了一個叫 reject 的 callback function,雖然它叫 reject,但它其實是 Promise 成功時才會執行的 callback function,因為這裡綜合了以上所提到的兩個特性:
- resolve 與 reject 可任意命名
- 第二個 reject 函式可放可不放
因為 executor 裡面只有一個 callback functoin,所以它其實代表的是 Promise 成功時才會執行的接口,也就是一般所認知的 resolve 函式。
then()
then()
方法回傳一個 Promise 物件。它接收兩個引數: Promise 在成功及失敗情況時的 callback function。
then()
第一個 callback function 是 Promise 對象的狀態變為 resolved 時調用,第二個 callback function 是 Promise 對象的狀態變為 rejected 時調用,其中第二個 callback function 是可選的,不一定要提供。這兩個函數都接受 Promise 對象傳出的值作為參數。
1 | const myPromise = new Promise(function(resolve, reject){ |
then() 串連
還記得我們使用 Promise 的初衷嗎?
為了讓非同步程式以同步程式操作的流程進行,我們使用 then 方法,依序呼叫兩個以上的非同步函數,而每個 then 方法回傳一個 Promise 以進行方法串接(method chaining),我們稱之為建立 Promise 鏈。
1 | myPromise.then(function(json) { |
catch()
catch 與 then 用法相反,它接收 Promise 失敗時的 reject 函式,一般放在 Promise 鏈的最後。1
2
3
4
5
6
7
8
9
10
11const myPromise = new Promise(function(resolve, reject){
// do something
resolve(value)
reject(reason)
})
myPromise.then(function(value){
// myPromise 狀態為成功時,執行此處程式碼
}.catch(function(){
// myPromise 狀態為失敗時,執行此處程式碼
}))
用 catch 定義 reject,別用 then 的第二個 callback function
一般來說,盡量不要在 then 方法裡面定義 Reject 狀態的 callback function(即 then 的第二個參數),建議使用 catch 方法。
1 | // bad |
實作
接下來讓我們來實作一個簡單的 promise 範例。
1 | function logWord(word){ |
function logWord 是一個 setTimeout 程式,會 不定時 console 出我們自定的參數,當我們用一個函式 allWord() 把數個 logWord() 包起來,執行時會發現裡面的 logWord 是隨機觸發的,原因是因為Math.floor(Math.random() * 1000)
。
但你想要 allWord() 裡面的 logWord() 是依序執行出 a b c,而不是隨機 b c a 或 c a b 時,我們會怎麼做?以前的做法是使用 callback 函式。
1 | function logWord(word, cb){ //插入 callback |
在 logWord() 插入一個 callback 參數(這裡取名為 cb) ,然後在 setTimeout 主程式的最後一行執行它。
接著執行以下動作:
- 在 allWord() 裡,將第一個執行的 logWord(‘a’) 插入 callback 當作第二個參數
- 把 logWord(‘b’) 放在 logWord(‘a’)的 callback,確保會在 console 出 a 才會執行 logWord(‘b’)
- 把 logWord(‘c’) 放在 logWord(‘b’)的 callback,確保會在 console 出 a 才會執行 logWord(‘c’)
- logWord(‘c’) callback fuction 為空值,因為此時沒其他事要做了
- 執行 allWord(),印出 a b c
這時你的程式就會依序印出 a b c 了。
但看看上面的程式碼,我們光只是寫三層 callback 就很累了,更別說之後萬一需要寫更多層的時候了,這時就會陷入萬劫不復的 callback hell (回呼地獄)
1 | function callback(hell){ |
我們用 Promise 來改寫一下上面的程式。
首先在原本的 logWord 返回一個新建的 Promise,語法為本文最一開始的 new Promise( function(resolve, reject) { … } )
然後把 setTimeout 主程式放入 Promise 裡。為了讓我們的 logWord 可以串連,我們在 setTimeout 主程式最後一行放入 resolve(),讓這個 Promise 物件成功執行時,馬上拋出 resolve。
1 | function logWord(word){ |
接著我們在 allWord() 裡,用 then 把成功執行的 logWord() 串連起來
1 | function allWord(){ |
為了簡潔美觀,我把它改成箭頭函式的寫法。
1 | function allWord(){ |
最後的成果,這樣就可依序印出 a b c
1 | function logWord(word){ |
總結
使用 Promise 讓我們可以把非同步操作以同步操作的流程表達出來,它把執行程式和處理結果的程式清晰地分離,同時簡潔的語法,也解決了以前多層 callback function 不好維護的問題。
對於 Promise 我其實還有很多未整理出來的地方,但希望透過這篇文章讓我對我目前所了解的 Promise 做個梳理,有興趣的朋友也可以參考下面所附連結進而更了解 Promise。
參考連結:
React 16 - The Complete Guide
你所不知道的JS:ES6與未來發展
javascript callback functions tutorial
Promises - Part 8 of Functional Programming in JavaScript
[JavaScript 教學] Callback與Promise (非同步編程基礎)
JavaScript Promise 漂亮的串接非同步事件
MDN - Promise
從Promise開始的JavaScript異步生活
Javascript的非同步之旅
JavaScript Promise:簡介
JAVASCRIPT.INFO - Promise