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调用时,用rreject 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方法内部需要实现的逻辑。