漢譯Promises/A+規範
第一次翻譯技術文章。原文在此,另有一篇Differences from Promises/A未譯。
Promises/A+
為Javascript的promises—by實現及其實現者所制定的開放標準,以期所有實現能達到健全一致的效果。
promise表示異步操作的最終結果。與promise交互的主要方式是通過其then
函數,註冊回調函數,從而對promise被執行後的value,或者被reject後的reason做進一步處理。
本規範定義了then
函數的行為,以期提供一個藍本,讓所有遵循Promises/A+的promise的實現有本可依。故此,本規範應該保持穩定。即使Promises/A+組織可能偶爾修訂本規範,加入一些向後兼容的小修改,來覆蓋新發現的邊界問題,但我們只會在經過認真思考論證和測試之後,才會加入比較大的或者向後不兼容的修改。
Promises/A+繼承修改了早期Promises/A proposal的主要內容, 并根據實際(de facto)擴展了一些內容,還去掉了那些尚未成為正式規範,或者有問題的部分。
要之,Promises/A+規範旨在指導實現者如何提供一個then
函數,而非如何create,fulfill,或者reject一個promises。將來,本規範的同族規範則會涉及這些議題。
術語
- "promise",一個具有
then
函數的對象或者函數,其行為符合本規範。 - "thenable",一個具有
then
的對象或者函數。 - "value",一個JavaScript對象(包括
undefined
,一個thenable對象,或者一個promise)。 - "exception",用
throw
語句拋出的對象。 - "reason",用以表示promise被reject的原因的值。
要素
promise的狀態
以下三種狀態,promise必居其一:pending, fulfilled, rejected。
- 於pending狀態,則promise:
- 可以轉換為fulfilled或者rejected狀態。
- 於fulfilled狀態,則promise:
- 不得再轉換為其他任何狀態。
- 必須有一個value,且不可更改。
- 於rejected狀態,則promise:
- 不得轉為其他狀態。
- 必須有一個reason,且不可更改。
兹所謂“不可更改”者,惟變量的指針不可易也(滿足===
),非該對象的屬性不可易。
then
函數
promise必須提供一個then
函數,來操作其產生的value或者reason。
then
函數接受2個參數:
promise.then(onFulfilled, onRejected)
-
onFulfilled
和onRejected
是可選參數:- 如果
onFulfilled
不是函數,則應該忽略之。 - 如果
onRejected
不是函數,則應該忽略之。
- 如果
-
如果
onFulfilled
是函數:promise
被fullfill之後必須調用之,且以promise
的value作為第一個參數。promise
被fulfill之前,不得調用之。- 只能調用一次。
-
如果
onRejected
是函數,promise
被reject之後必須調用之,且以promise
的reason作為第一個參數。promise
被reject之前,不得調用之。- 只能調用一次。
-
onFulfilled
和onRejected
必須在execution context堆棧中只剩下platform code之後才能執行。[注釋1] -
onFulfilled
與onRejected
必須作為函數調用。(不能帶this
值)。[注釋2] -
then
可以在一個promise中被多次調用。- 如果
promise
轉為fulfilled狀態,所有通過then
綁定的onFulfilled
回調函數必須按照順序執行。 - 如果
promise
轉為rejected狀態,所有通過then
綁定的onRejected
回調函數必須按照順序執行。
- 如果
-
then
的返回值必須為promise[注釋3].promise2 = promise1.then(onFulfilled, onRejected);
- 如果
onFulfilled
和onRejected
都會返回x
,則執行Promise Resolution Procedure[[Resolve]](promise2, x)
。 - 如果
onFulfilled
和onRejected
都會拋出異常e
,promise2
必須被reject,且以e
作為reason。 - 如果
onFulfilled
不是函數,並且promise1
被fulfill,則promise2
必須也被fulfill,其value與promise1
相同。 - 如果
onRejected
不是函數,並且promise1
被reject,則promise2
必須也被reject,其reason與promise1
相同。
- 如果
The Promise Resolution Procedure
promise resolution procedure是一個抽象的操作,需要傳入兩個參數,promise和x,可表示為[[Resolve]](promise, x)
。如果x
是一個thenable,實際上x
在某種程度上就是一個promise,此時應該嘗試用x
的狀態來替換promise
的狀態。否則,以x
為value,fulfill這個promise
。
這種處理方式具有一個好處,如果某個對象具有符合Promises/A+規範的then
函數,他就可以跟promise進行交互。從而讓Promises/A+實現可以吸納(assimilate)不兼容Promises/A+規範但卻具有then
函數的實現。
[[Resolve]](promise, x)
的行為,需遵循以下步驟:
- 如果
promise
與x
指向同一個對象,用TypeError
作為reason,reject這個promise
。 - 如果
x
是一個promise,則採用其狀態。[注釋4]:- 如果
x
處於pending狀態,則promise
必須保持pending狀態,直到x
被fulfill或者reject。 - 如果
x
被fulfill,用同樣的value來fulfillpromise
。 - 如果
x
被reject,用同樣的reason來rejectpromise
。
- 如果
- 此外,如果
x
是一個對象或者函數,- 使
then
指向x.then
。[注釋5] - 如果獲取
x.then
的時候捕獲異常e
,則用e
作為reason來reject這個promise
。 - 如果
then
是函數,則調用他,以x
作為this
,第一個參數為resolvePromise
,第二個為rejectPromise
,並且:- 如果
resolvePromise
被執行,參數為y
,則運行[[Resolve]](promise, y)
。 - 如果
rejectPromise
被執行,參數是r
,則用r
來reject這個promise
。 - 如果
resolvePromise
和rejectPromise
都被調用,或者他們被重複調用,並且參數是一樣的,則只有第一個調用生效,其他被忽略。 - 如果調用
then
時捕獲異常e
,- 如果
resolvePromise
或者rejectPromise
已經執行了,則忽略之。 - 否則,用
e
作為reason,reject該promise
。
- 如果
- 如果
- 如果
then
不是函數,以x
為value,fulfill該promise
。
- 使
- 如果
x
不是對象或者函數,以x
為value,fulfill該promise
。
如果resolve的第二個參數是一個處在循環鏈中的thenable,其中的遞歸邏輯會導致[[Resolve]](promise, thenable)
被反復調用,按照上面的算法,最終會陷入死循環。Promises/A+實現應該檢測這種循環關係,並且以TypeError
作為reason,reject之。是為非強制性要求。[注釋6]
Notes
-
Here "platform code" means engine, environment, and promise implementation code. In practice, this requirement ensures that
onFulfilled
andonRejected
execute asynchronously, after the event loop turn in whichthen
is called, and with a fresh stack. This can be implemented with either a "macro-task" mechanism such assetTimeout
orsetImmediate
, or with a "micro-task" mechanism such asMutationObserver
orprocess.nextTick
. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or "trampoline" in which the handlers are called. -
That is, in strict mode
this
will beundefined
inside of them; in sloppy mode, it will be the global object. -
Implementations may allow
promise2 === promise1
, provided the implementation meets all requirements. Each implementation should document whether it can producepromise2 === promise1
and under what conditions. -
Generally, it will only be known that
x
is a true promise if it comes from the current implementation. This clause allows the use of implementation-specific means to adopt the state of known-conformant promises. -
This procedure of first storing a reference to
x.then
, then testing that reference, and then calling that reference, avoids multiple accesses to thex.then
property. Such precautions are important for ensuring consistency in the face of an accessor property, whose value could change between retrievals. -
Implementations should not set arbitrary limits on the depth of thenable chains, and assume that beyond that arbitrary limit the recursion will be infinite. Only true cycles should lead to a
TypeError
; if an infinite chain of distinct thenables is encountered, recursing forever is the correct behavior.