Fwio

Promise/A+ 规范

7 min

I reprint this article in Chinese, just like Prettier.

Reference:

1. Terminology

  1. “promise” 是一个对象或者函数,其 then 方法的实现与该规范一致。
  2. “thenable” 是一个定义了 then 方法的对象或者函数。

    注意:在 ECMAScript 规范中,只要一个对象定义then 方法,无论实现如何,都被认为实现了 Thenable 接口。

  3. “value” 是任意 JavaScript 值(包含 undefinedthenablepromise)。
  4. “exception” 是一个由 throw 语句抛出的值。
  5. “reason” 是解释一个 promise 变为 rejected 的原因。

2. Requirements

2.1 Promise States

一个 Promise 必须是以下一种状态:pendingfulfilledrejected.

  1. 当处于 pending,promise :
    1. 可能转变为 fulfilledrejected 状态。
  2. 当处于 fulfilled,promise :
    1. 不能再转变为其他状态;
    2. 必须有一个结果值 value,且这个值不可改变。
  3. 当处于 rejected,promise:
    1. 不能再转变为其他状态;
    2. 必须有一个原因 reason,且这个原因不可改变。

这里的“不可改变”即 Strict Equality,但不要求深不可变性(Deep Immutability)。

2.2 The then Method

一个 promise 必须提供一个 then 方法,以访问其当前或最终的结果值(value)或原因(reason)。

Promise 的 then 方法接收两个参数:

promise.then(onFulfilled, onRejected)
  1. onFulfilledonRejected 都是可选参数:

    1. 如果 onFulfilled 不是函数,其应该被忽略;
    2. 如果 onRejected 不是函数,其应该被忽略。
  2. 如果 onFulfilled 是函数:

    1. 它必须在 promise 变为 fulfilled 后被调用,并以 promise 的结果值(value)作为第一个参数;
    2. 在 promise 变为 fulfilled 之前不能被调用;
    3. 最多只被调用一次。
  3. 如果 onRejected 是函数:

    1. 它必须在 promise 变为 rejected 后被调用,并以 promise 的理由(reason)作为第一个参数;
    2. 在 promise 变为 rejected 之前不能被调用;
    3. 最多只被调用一次。
  4. onFulfilledonRejected 在执行上下文栈只包含平台代码之前不可被调用。

平台代码(Platform code)是指引擎、环境和 promise 实现代码。实际上,该要求保证 onFulfilledonRejected 在调用 then 后被异步执行。这可以通过宏任务(Task)机制实现(比如 setTimeoutsetImmediate),或者微任务(Microtask)机制(比如 queueMicrotaskprocess.nextTick)。

  1. onFulfilledonRejected 必须作为函数调用(没有 this 对象)。

  2. 在同一个 promise 上,then 可能被多次调用。

    1. 当 promise 处于 fulfilled 状态,所有相关的 onFulfilled 必须按照其 then 的调用顺序执行;
    2. 当 promise 处于 rejected 状态,所有相关的 onRejected 必须按照其 then 的调用顺序执行。
  3. then 必须返回一个 promise。

    1. promise2 = promise1.then(onFulfilled, onRejected)
    2. 如果 onFulfilledonRejected 返回了一个值 xpromise2 执行 promise resolution procedure [[Resolve]](promise2, x)

    3. 如果 onFulfilledonRejected 抛出一个异常 epromise2 转变为 rejected,其原因为e

    4. 如果 onFulfilled 不是函数,且 promise1 转为了 fulfilled,则 promise2 转为 fulfilled,其结果值(value)与 promise1 一样;

    5. 如果 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),会执行以下步骤:

  1. 如果 promisex 指向同一个对象,则以 TypeError 作为原因 reject 该 promise;
  2. 如果 x 是一个 promise,采用(adopt)其状态:
    1. 如果 x 处于 pending,promise 保持 pending 直到 x 状态改变;
    2. x 转为 fulfilled,使 promise 以相同的值转为 fulfilled
    3. x 转为 rejected,使 promise 以相同的原因转为 rejected
  3. 否则,如果 x 是一个对象或函数:
    1. 声明 then = x.then
    2. 如果 x.then 导致抛出一个异常 e,将 e 作为原因 reject promise
    3. 如果 then 是一个函数,将 x 作为其 this 对象,传入第一个参数 resolvePromise 和第二个参数 rejectPromise,调用 then
      1. resolvePromisey 值调用时,运行 [[Resolve]](promise, y)
      2. rejectPromise 以原因 r 调用时,用 r reject promise;
      3. 如果 rejectPromiseresolvePromise 都被调用,或者一者被多次调用,只取第一次调用的结果,并忽视后续调用;
      4. 如果调用 then 抛出异常 e
        1. 如果 resolvePromiserejectPromise 已被调用,则忽视异常;
        2. 否则,以 e 为原因 reject promise。
    4. 如果 then 不是函数,则以 x 为值将 promise 转为 fulfilled
  4. 如果 x 不是函数或对象,则以 x 为值将 promise 转为 fulfilled。(这里包括 x === undefined,即 handler 没有返回值的情况)

当上面这种实现遇到循环引用的 thenable 对象时,将进入无限递归,推荐但不要求对这种情况以 TypeError 为原因 reject promise。

注意:规约中并未提到 promise 本身的状态迁移方法(即我们熟悉的 ECMAScript 中的 resolvereject)该如何实现, 在 ECMAScript 中,promise 构造函数中的 resolve 也会对 thenable 类型的结果值进行解包(Unwrap), 所以可以大致将上面的 [[Resolve]](promise, x) 视作 promise 的 resolve 以及 reject 方法内部需要实现的逻辑。