Fwio

Promise/A+ 规范

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 必须是以下一种状态:pending,fullfied 和 rejected.

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

这里的”不可改变”意为 immutable,即 JavaScript 全等===,但不要求深不可变(deep immutability)。

2.2 The then Method

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

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

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

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

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

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

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

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

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

    1. promise是 fullfied,所有相关的onFulfilled必须按照其then的调用顺序执行。
    2. promise是 rejected,所有相关的onRejected必须按照其then的调用顺序执行。
  3. then必须返回一个 promise。

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

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

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

    5. 如果onRejected不是函数,且promise1转为了 rejected,则promise2转为 rejected,其原因(reason)与promise1一样。

注意: 根据具体实现的不同,then方法返回的 promise 可能与原 promise 完全相等。

2.3 The Promise Resolution Procedure

Promise 解析流程(promise pesolution procedure)是一个抽象操作,它接收一个 promise 和值(value)作为输入,记为[[Resolve]](promise, x)。如果x是一个 thenable,那么在x与 promise 具有类似行为的估量下,promise将采用x的状态。否则,promise将以x为结果,进入 fulllfied。

对于 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转为 fullfied,使promise以相同的值转为 fullfied。
    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转为 fullfied。
  4. 如果x不是函数或对象,则以x为值将promise转为 fullfied。(这里包括x === undefined,即 handler 没有返回值的情况)

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

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