術語
Promise
promise 是一個擁有 then 方法的對象或函數,其行為符合本規范;
thenable
是一個定義了 then 方法的對象或函數,文中譯作“擁有 then 方法”;
值(value)
指任何 JavaScript 的合法值(包括 undefined , thenable 和 promise);
異常(exception)
是使用 throw 語句拋出的一個值。
拒絕原因(reason)
表示一個 promise 的拒絕原因。
要求
等待態(Pending)
處於等待態時,promise 需滿足以下條件:
- 可以遷移至完成態或拒絕態
- 不能遷移至其他任何狀態
- 必須擁有一個不可變的終值
拒絕態(Rejected)
處於拒絕態時,promise 需滿足以下條件:
- 不能遷移至其他任何狀態
- 必須擁有一個不可變的據因
這里的不可變指的是恆等(即可用 === 判斷相等),而不是意味着更深層次的不可變(譯者注: 蓋指當 value 或 reason 不是基本值時,只要求其引用地址相等,但屬性值可被更改)。
Then 方法
一個 promise 必須提供一個 then 方法以訪問其當前值、終值和據因。
then 方法接受兩個參數:
onFulfilled 和 onRejected 都是可選參數。
- 如果
onFulfilled不是函數,其必須被忽略 - 如果
onRejected不是函數,其必須被忽略
onFulfilled 特性
如果 onFulfilled 是函數:
- 當
promise執行結束后其必須被調用,其第一個參數為promise的終值 - 在
promise執行結束前其不可被調用 - 其調用次數不可超過一次
onRejected 特性
如果 onRejected 是函數:
- 當
promise被拒絕執行后其必須被調用,其第一個參數為promise的據因 - 在
promise被拒絕執行前其不可被調用 - 其調用次數不可超過一次
調用時機
onFulfilled 和 onRejected 只有在執行環境堆棧僅包含平台代碼時才可被調用 注1
調用要求
多次調用
then 方法可以被同一個 promise 調用多次
- 當
promise成功執行時,所有onFulfilled需按照其注冊順序依次回調 - 當
promise被拒絕執行時,所有的onRejected需按照其注冊順序依次回調
返回
onFulfilled
或者
onRejected
返回一個值
x
,則運行下面的
Promise 解決過程
:
[[Resolve]](promise2, x)
- 如果
onFulfilled或者onRejected拋出一個異常e,則promise2必須拒絕執行,並返回拒因e - 如果
onFulfilled不是函數且promise1成功執行,promise2必須成功執行並返回相同的值 - 如果
onRejected不是函數且promise1拒絕執行,promise2必須拒絕執行並返回相同的據因
Promise 解決過程
Promise 解決過程 是一個抽象的操作,其需輸入一個 promise 和一個值,我們表示為 [[Resolve]](promise, x),如果 x 有then 方法且看上去像一個 Promise ,解決程序即嘗試使 promise 接受 x 的狀態;否則其用 x 的值來執行 promise 。
這種 thenable 的特性使得 Promise 的實現更具有通用性:只要其暴露出一個遵循 Promise/A+ 協議的 then 方法即可;這同時也使遵循 Promise/A+ 規范的實現可以與那些不太規范但可用的實現能良好共存。
運行 [[Resolve]](promise, x) 需遵循以下步驟:
x 與 promise 相等
如果 promise 和 x 指向同一對象,以 TypeError 為據因拒絕執行 promise
x 為 Promise
如果 x 為 Promise ,則使 promise 接受 x 的狀態 注4:
- 如果
x處於等待態,promise需保持為等待態直至x被執行或拒絕 - 如果
x處於完成態,用相同的值執行promise - 如果
x處於拒絕態,用相同的據因拒絕promise
.then(function(data){
.then(function(data){
console.log(data);
return runAsync3();
})
x 為對象或函數
如果 x 為對象或者函數:
- 把
x.then賦值給then注5 - 如果取
x.then的值時拋出錯誤e,則以e為據因拒絕promise - 如果
then是函數,將x作為函數的作用域this調用之。傳遞兩個回調函數作為參數,第一個參數叫做resolvePromise,第二個參數叫做rejectPromise: -
- 如果
resolvePromise以值y為參數被調用,則運行[[Resolve]](promise, y) - 如果
rejectPromise以據因r為參數被調用,則以據因r拒絕promise - 如果
resolvePromise和rejectPromise均被調用,或者被同一參數調用了多次,則優先采用首次調用並忽略剩下的調用 - 如果調用
then方法拋出了異常e: -
- 如果
resolvePromise或rejectPromise已經被調用,則忽略之 - 否則以
e為據因拒絕promise
- 如果
- 如果
then不是函數,以x為參數執行promise
- 如果
- 如果
x不為對象或者函數,以x為參數執行promise
如果一個 promise 被一個循環的 thenable 鏈中的對象解決,而 [[Resolve]](promise, thenable) 的遞歸性質又使得其被再次調用,根據上述的算法將會陷入無限遞歸之中。算法雖不強制要求,但也鼓勵施者檢測這樣的遞歸是否存在,若檢測到存在則以一個可識別的TypeError 為據因來拒絕 promise 注6。
注釋
-
注1 這里的平台代碼指的是引擎、環境以及 promise 的實施代碼。實踐中要確保
onFulfilled和onRejected方法異步執行,且應該在then方法被調用的那一輪事件循環之后的新執行棧中執行。這個事件隊列可以采用“宏任務(macro-task)”機制或者“微任務(micro-task)”機制來實現。由於 promise 的實施代碼本身就是平台代碼(譯者注: 即都是 JavaScript),故代碼自身在處理在處理程序時可能已經包含一個任務調度隊列或『跳板』)。譯者注: 這里提及了 macrotask 和 microtask 兩個概念,這表示異步任務的兩種分類。在掛起任務時,JS 引擎會將所有任務按照類別分到這兩個隊列中,首先在 macrotask 的隊列(這個隊列也被叫做 task queue)中取出第一個任務,執行完畢后取出 microtask 隊列中的所有任務順序執行;之后再取 macrotask 任務,周而復始,直至兩個隊列的任務都取完。
兩個類別的具體分類如下:
-
- macro-task: script(整體代碼),
setTimeout,setInterval,setImmediate, I/O, UI rendering -
micro-task:
process.nextTick,Promises(這里指瀏覽器實現的原生 Promise),Object.observe,MutationObserver詳見 stackoverflow 解答 或 這篇博客
- macro-task: script(整體代碼),
-
注2 也就是說在 嚴格模式(strict) 中,函數
this的值為undefined;在非嚴格模式中其為全局對象。 -
注3 代碼實現在滿足所有要求的情況下可以允許
promise2 === promise1。每個實現都要文檔說明其是否允許以及在何種條件下允許promise2 === promise1。 -
注4 總體來說,如果
x符合當前實現,我們才認為它是真正的 promise 。這一規則允許那些特例實現接受符合已知要求的 Promises 狀態。 -
注5 這步我們先是存儲了一個指向
x.then的引用,然后測試並調用該引用,以避免多次訪問x.then屬性。這種預防措施確保了該屬性的一致性,因為其值可能在檢索調用時被改變。 -
注6 實現不應該對 thenable 鏈的深度設限,並假定超出本限制的遞歸就是無限循環。只有真正的循環遞歸才應能導致
TypeError異常;如果一條無限長的鏈上 thenable 均不相同,那么遞歸下去永遠是正確的行為。
