Promises/A+
這是一個開放標準,旨在讓不同開發者實現的 JavaScript Promise 能夠無縫銜接並應用——由前輩們制定,為其他後來者提供參考
一個 promise 所表示的是非同步操作的結果。與 promise 互動的主要方式是透過它的 then
方法,該方法會註冊所傳入的回撥函式,回撥函式將接收 promise 的最終值或者 promise 無法被滿足時的原因。
本規範詳細說明了 then
方法的行為,為所有符合 Promises/A+ 標準的 promise 實現提供可靠的基礎。因此本規範的更新或修訂應被視為非常穩定。雖然 Promises/A+ 組織可能會偶爾對本規範進行向後相容的小幅度修訂,以解決新發現的邊緣情況,但我們依然會在經過認真仔細的考慮、討論和測試後,才會去整合重大或者不向後相容的變更。
在過去,Promises/A+ 不僅明確早期 Promise/A 提案中的涉及的行為條款,而且還擴充套件了一些涵蓋實際行為的內容,並且省略了那些不清不楚或存在問題的部分。
最後要說的是,Promises/A+ 規範的核心並不涉及如何建立、滿足或拒絕 promise,而是選擇專注於提供一個強大的 then
方法以確保不同實現之間的相容性。大概未來的相關規範才可能會涉及如何建立、滿足和拒絕 promise 這些主題吧。
1 專業術語
thenable
: 一個具有then
方法的物件或函式。promise
:一個具有then
方法的物件或函式,then
方法的行為符合本規範。value
:是一個合法的 JS 值,含undefined
、null
、thenable
或promise
等。exception
:透過throw
語句丟擲的值。reason
:通常用於表示promise
被拒絕/無法實現的原因,也是個值。
2 詳細規範
2.1 Promise 狀態
一個 promise 必須處於以下三種狀態之一:
待定中(pending)
、已實現(fulfilled)
、已拒絕(rejected)
關於這些狀態的描述如下:
- 待定中 —— pending:
- 可以轉變為其它兩種狀態,也就是已實現(fulfilled)或已拒絕(rejected)
- 已實現 —— fulfilled:
- 禁止從該狀態轉變為其它狀態,狀態不可改變
- 必須有一個不可改變的值(value —— 完成後的成果!!)
- 已拒絕 —— rejected:
- 禁止從該狀態轉變為其它狀態,狀態不可改變
- 必須有一個不可改變的原因(reason —— 為什麼拒絕!?)
注意:
不可改變
並不意味著深層次的不可變(比如屬性描述符中writeable: false
),你可以透過對當前狀態進行全等判斷(===
),從而更改相應的值/原因和狀態後,那麼不可改變
自然而然的就會成立了。
2.2 then
方法
一個 promise 必須提供一個 then
方法,它可以訪問當前或最終的值或者原因。
promise 的 then
方法接受兩個引數:
promise.then(onFulfilled, onRejected)
-
onFulfilled
和onRejected
均是可選引數:- 如果
onFulfilled
不是一個函式,則必須被忽略。 - 如果
onRejected
不是一個函式,則必須被忽略。
被忽略
是指我們不對它做任何的處理,不會丟擲任何的錯誤 - 如果
-
如果
onFulfilled
是一個函式:- 必須在
promise
被實現後呼叫,且以promise
的值(value)作為第一個引數。 - 在
promise
被實現之前不可呼叫。 - 不允許被多次呼叫。
- 必須在
-
如果
onRejected
是一個函式:- 必須在
promise
被拒絕後呼叫,且以promise
的原因(reason)作為第一個引數。 - 在
promise
被拒絕之前不可呼叫。 - 不允許被多次呼叫。
- 必須在
-
onFulfilled
或onRejected
必須在執行上下文棧僅包含平臺程式碼時才被呼叫。[3.1] -
onFulfilled
和onRejected
必須作為函式呼叫(沒有this
值)。[3.2] -
then
可以在同一個 promise 上被多次呼叫。- 當
promise
被實現時,所有相應的onFulfilled
回撥必須按它們呼叫then
的順序執行。 - 當
promise
被拒絕時,所有相應的onRejected
回撥必須按它們呼叫then
的順序執行。
- 當
-
then
必須返回一個 promise [3.3]。promise2 = promise1.then(onFulfilled, onRejected);
- 如果
onFulfilled
或onRejected
返回一個值x
,則執行 Promise 解決過程[[Resolve]](promise2, x)
[2.3]。 - 如果
onFulfilled
或onRejected
丟擲異常e
,則promise2
必須以e
作為原因被拒絕。 - 如果
promise1
被實現且onFulfilled
不是一個函式,那麼promise2
必須以與promise1
相同的值(value)被實現。 - 如果
promise1
被拒絕且onRejected
不是一個函式,那麼promise2
必須以與promise1
相同的原因(reason)被拒絕。
- 如果
2.3 Promise 解決過程
Promise 解決過程 是一種抽象操作,它接受一個 promise 和一個值作為輸入,表示為 [[Resolve]](promise, x)
。如果 x
是一個 thenable,則會嘗試使 promise
採用 x
的狀態,前提是 x
至少在某種程度上表現得像一個 promise。否則,它將用值 x
來實現 promise
。
對 thenable 的這種處理方式使得不同的 promise 實現能夠有效相容,只要它們提供符合 Promises/A+ 標準的 then
方法。但是也允許 Promises/A+ 實現能夠“接納”那些具有合理 then
方法的非標準實現。
要實現 [[Resolve]](promise, x)
,請按照以下步驟操作:
- 如果
promise
和x
指向同一個物件,則以TypeError
拒絕promise
作為原因。 - 如果
x
是一個 promise,則採用其狀態 [3.4]:- 如果
x
是待定狀態,promise
必須保持待定,直到x
被實現或拒絕。 - 如果
x
被實現,使用相同的值實現promise
。 - 如果
x
被拒絕,使用相同的原因拒絕promise
。
- 如果
- 否則,如果
x
是一個物件或函式:- 將
then
設定為x.then
。[3.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
。[3.6]
3 引注
-
這裡的“平臺程式碼”指的是引擎、環境和 promise 的實現程式碼。實際上,這一要求確保
onFulfilled
和onRejected
在呼叫then
的事件迴圈轉之後非同步執行,並且在一個新的呼叫棧中。這可以透過“宏任務”機制(如setTimeout
或setImmediate
)或“微任務”機制(如MutationObserver
或process.nextTick
)來實現。由於 promise 實現被視為平臺程式碼,它本身可能包含一個任務排程佇列或“跳板”,用於呼叫處理程式。 -
也就是說,在嚴格模式下,
this
的值將是undefined
;在非嚴格模式下,this
將指向全域性物件。 -
實現可以允許
promise2 === promise1
,前提是該實現滿足所有要求。每個實現應記錄是否可以產生promise2 === promise1
以及在什麼條件下可以實現。 -
通常,只有當
x
來自當前實現時,才能確定x
是一個真正的 promise。這一條款允許使用特定於實現的方法來採用已知符合標準的 promises 的狀態。 -
這個過程首先儲存對
x.then
的引用,然後測試該引用,最後呼叫該引用,避免了對x.then
屬性的多次訪問。這種預防措施對於確保在訪問器屬性的情況下保持一致性非常重要,因為該屬性的值可能在多次檢索之間發生變化。 -
實現不應對 thenable 鏈的深度設定任意限制,並假設超出該限制的遞迴將是無限的。只有真正的迴圈才應導致
TypeError
;如果遇到無限的不同 thenable 鏈,則無限遞迴是正確的行為。