promise 之所以比 callback 直覺、簡單,是因為它的概念是:
也就是 producing code 花時間去產生預期結果,promise 確保讓需要的地方都可以拿到。
建構 promise 物件:
let promise = new Promise(function(resolve, reject) {
// 這個函式是 executor (the producing code)
});
new Promise
建構式中傳入的函式稱為 executor,它裡面要放的就是 producing code。當一個新 promise 物件建立時 (建構式回傳 promise 物件前),executor 會立刻自動執行,它的兩個參數 resolve
和 reject
是 JS 提供的 callback,當 producing code 取得結果後,應該呼叫其中一個函式:
另外,由建構式回傳的 promise 有幾個內建屬性:
resolve
的話變為 'fulfilled',呼叫 reject
則變為 'rejected'。undefined
,呼叫 resolve
的話變為結果值,呼叫 reject
則變為 error。以下是任務成功完成的例子,executor 在一秒後呼叫了 resolve
,promise 的狀態從 'pending' 變成了 'fulfilled':
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve('done'), 1000);
});
這是失敗時 executor 呼叫 reject
送出錯誤的例子,promise 的狀態從 'pending' 變成了 'rejected':
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error('Whoops!')), 1000);
});
換句話說,executor 內執行可能要花點時間的任務,最終會藉由呼叫 resolve
或 reject
來改變 promise 物件的狀態。
成功 (fulfilled) 或失敗 (rejected) 的 promise 統稱為 settled,代表有確定的結果。settled 只是相對於 pending 的一種表達,並不是正式的狀態。
只會有一個成功結果或一個錯誤
executor 只應呼叫一次 resolve
或 reject
,狀態改變就成定局,之後再呼叫都會被忽略。此外,resolve
或 reject
也都只能有一個 (或沒有) 參數,多個就被忽略。
let promise = new Promise(function(resolve, reject) {
resolve('done');
reject(new Error('…')); // 被忽略
setTimeout(() => resolve('…')); // 被忽略
});
錯誤物件
失敗的情況時,reject
其實可以和 resolve
一樣傳入任何參數,但建議使用 Error
物件。
可以馬上呼叫 resolve
或 reject
嗎?
executor 內通常會處理非同步任務,但不一定非得處理非同步,它也可以馬上呼叫 resolve
或 reject
:
let promise = new Promise(function(resolve, reject) {
resolve(123);
});
這種情況可能用在開始任務前的檢查,若發現已經完成,就可以直接回傳成功的 promise。
promise 是因為適合所以才拿來處理非同步,而不是它只能放非同步的工作或會把工作變非同步。這聽起來非常簡單合理,但人有時就是會莫名迷失,像我就曾經指著自己寫在 promise 裡的東西問隔壁同事:這到底是同步還是非同步@@ (然後他也答不出來)。
promise 物件的狀態
上面提到 promise 物件的 state
和 result
屬性無法直接調用。要得到 promise 的結果要使用特定方法,這就跟接下來要寫的 consuming code 如何接到結果有關。
如果上面內容已經讓你的人生很快樂,建議不要讀這段。
MDN 裡面提到,一般口語表達中 resolved promises 和 fulfilled promises 基本上是同樣的意思 (這不難理解,因為在呼叫 resolve
後會得到 fulfilled promise)。但根據這份 States and Fates 文件,所謂 resolved promise 它的狀態也可以是 rejected 或 pending,也就是 resolved 是相對於 unresolved 而言,跟狀態無關,而這裡的 resolved 是指「當對 promise 呼叫 resolve
或 reject
不會改變它的狀態」。例如當 promise 狀態已經是 fulfilled 或 rejected,就像上面說的,再次呼叫也沒有影響。或者像這個例子:
new Promise((resolveOuter) => {
resolveOuter(
new Promise((resolveInner) => {
setTimeout(resolveInner, 1000);
}),
);
});
外面的 promise 在建立的時候就已經 resolved,因為 executor 直接同步呼叫了 resolve
,但是因為它要等裡面的 promise,所以狀態並不會改變 (仍然是 pending),一秒之後才會變成 fulfilled。
我不知道這段對於理解 promise 會是幫助還是阻礙,不過確實想起剛開始認識 promise 時,對於各種 resolved、fulfilled、settled...的表達感到混亂。所以雖然不同情境下的定義有細微差異讓人很煩躁,還是很高興可以讀到這樣的詳細解釋。