Promise/A+ 规范
I reprint this article in Chinese, just like Prettier.
Reference:
1. Terminology
- “promise” 是一个对象或者函数,其
then
方法的实现与该规范一致。 - “thenable” 是一个定义了
then
方法的对象或者函数。注意:在 ECMAScript 规范中,只要一个对象定义了
then
方法,无论实现如何,都被认为实现了Thenable
接口。 - “value” 是任意 JavaScript 值(包含
undefined
,thenable
或promise
)。 - “exception” 是一个由
throw
语句抛出的值。 - “reason” 是解释一个 promise 变为 rejected 的原因。
2. Requirements
2.1 Promise States
一个 Promise 必须是以下一种状态:pending
,fulfilled
和 rejected
.
- 当处于
pending
,promise :- 可能转变为
fulfilled
或rejected
状态。
- 可能转变为
- 当处于
fulfilled
,promise :- 不能再转变为其他状态;
- 必须有一个结果值
value
,且这个值不可改变。
- 当处于
rejected
,promise:- 不能再转变为其他状态;
- 必须有一个原因
reason
,且这个原因不可改变。
这里的“不可改变”即 Strict Equality,但不要求深不可变性(Deep Immutability)。
2.2 The then
Method
一个 promise 必须提供一个 then
方法,以访问其当前或最终的结果值(value
)或原因(reason
)。
Promise 的 then
方法接收两个参数:
promise.then(onFulfilled, onRejected)
-
onFulfilled
和onRejected
都是可选参数:- 如果
onFulfilled
不是函数,其应该被忽略; - 如果
onRejected
不是函数,其应该被忽略。
- 如果
-
如果
onFulfilled
是函数:- 它必须在 promise 变为
fulfilled
后被调用,并以 promise 的结果值(value
)作为第一个参数; - 在 promise 变为
fulfilled
之前不能被调用; - 最多只被调用一次。
- 它必须在 promise 变为
-
如果
onRejected
是函数:- 它必须在 promise 变为
rejected
后被调用,并以 promise 的理由(reason
)作为第一个参数; - 在 promise 变为
rejected
之前不能被调用; - 最多只被调用一次。
- 它必须在 promise 变为
-
onFulfilled
和onRejected
在执行上下文栈只包含平台代码之前不可被调用。
平台代码(Platform code)是指引擎、环境和 promise 实现代码。实际上,该要求保证
onFulfilled
和onRejected
在调用then
后被异步执行。这可以通过宏任务(Task)机制实现(比如setTimeout
或setImmediate
),或者微任务(Microtask)机制(比如queueMicrotask
或process.nextTick
)。
-
onFulfilled
和onRejected
必须作为函数调用(没有this
对象)。 -
在同一个 promise 上,
then
可能被多次调用。- 当 promise 处于
fulfilled
状态,所有相关的onFulfilled
必须按照其then
的调用顺序执行; - 当 promise 处于
rejected
状态,所有相关的onRejected
必须按照其then
的调用顺序执行。
- 当 promise 处于
-
then
必须返回一个 promise。-
promise2 = promise1.then(onFulfilled, onRejected)
-
如果
onFulfilled
或onRejected
返回了一个值x
,promise2
执行 promise resolution procedure[[Resolve]](promise2, x)
; -
如果
onFulfilled
或onRejected
抛出一个异常e
,promise2
转变为rejected
,其原因为e
; -
如果
onFulfilled
不是函数,且promise1
转为了fulfilled
,则promise2
转为fulfilled
,其结果值(value
)与promise1
一样; -
如果
onRejected
不是函数,且promise1
转为了rejected
,则promise2
转为rejected
,其原因(reason
)与promise1
一样。
-
注意:根据具体实现的不同,
then
方法返回的 promise 可能与原 promise 完全相等。
2.3 The Promise Resolution Procedure
Promise 解析流程(promise resolution procedure)是一个抽象操作,它接收一个 promise 和值(value
)作为输入,记为 [[Resolve]](promise, x)
。如果 x
是一个 thenable,那么在 x
与 promise 具有类似行为的估量下,promise 将采用 x
的状态。否则,promise 将以 x
为结果,进入 fulfilled
。
对于 thenable 的这种处理,使得 promise 的实现可以互操作(interoperate),即实现了满足 Promise/A+ 规范的 then
方法的对象就可以视作 promise。
运行 [[Resolve]](promise, x)
,会执行以下步骤:
- 如果
promise
和x
指向同一个对象,则以TypeError
作为原因 reject 该 promise; - 如果
x
是一个 promise,采用(adopt)其状态:- 如果
x
处于pending
,promise 保持pending
直到x
状态改变; - 当
x
转为fulfilled
,使 promise 以相同的值转为fulfilled
; - 当
x
转为rejected
,使 promise 以相同的原因转为rejected
。
- 如果
- 否则,如果
x
是一个对象或函数:- 声明
then = x.then
; - 如果
x.then
导致抛出一个异常e
,将e
作为原因 rejectpromise
; - 如果
then
是一个函数,将x
作为其this
对象,传入第一个参数resolvePromise
和第二个参数rejectPromise
,调用then
:- 当
resolvePromise
以y
值调用时,运行[[Resolve]](promise, y)
; - 当
rejectPromise
以原因r
调用时,用r
reject promise; - 如果
rejectPromise
和resolvePromise
都被调用,或者一者被多次调用,只取第一次调用的结果,并忽视后续调用; - 如果调用
then
抛出异常e
:- 如果
resolvePromise
或rejectPromise
已被调用,则忽视异常; - 否则,以
e
为原因 reject promise。
- 如果
- 当
- 如果
then
不是函数,则以x
为值将 promise 转为fulfilled
。
- 声明
- 如果
x
不是函数或对象,则以x
为值将 promise 转为fulfilled
。(这里包括x === undefined
,即 handler 没有返回值的情况)
当上面这种实现遇到循环引用的 thenable 对象时,将进入无限递归,推荐但不要求对这种情况以 TypeError
为原因 reject promise。
注意:规约中并未提到 promise 本身的状态迁移方法(即我们熟悉的 ECMAScript 中的
resolve
和reject
)该如何实现, 在 ECMAScript 中,promise 构造函数中的resolve
也会对 thenable 类型的结果值进行解包(Unwrap), 所以可以大致将上面的[[Resolve]](promise, x)
视作 promise 的resolve
以及reject
方法内部需要实现的逻辑。