返回博客归档

ISSUE / 008

[译]Promises/A+ 标准

Promises/A+ 标准 一个可靠的、可交互的 JavaScript Promises 开放标准 ——供实现者参考 一个 promise 表示一个异步操作的最终结果。和 promise 交互的主要方式是通过它的 then 方法,通过在 then 方法中注册回调函数来接收 promise 的最终值或者…

[译]Promises/A+ 标准的文章题图

Promises/A+ 标准

_20190214092805

一个可靠的、可交互的 JavaScript Promises 开放标准 ——供实现者参考

一个 promise 表示一个异步操作的最终结果。和 promise 交互的主要方式是通过它的 then 方法,通过在 then 方法中注册回调函数来接收 promise 的最终值或者无法实现的原因(拒因)。

本规范详尽描述了 then 方法的行为,为所有 Promises/A+ 的实现提供了一个可交互的基础。因此,该规范将是非常稳定的。尽管 Promises/A+ 组织 可能会偶尔通过修正(向后兼容的 minor 版本)该规范来解决一些新发现的边界情况。只有经过仔细的考虑、讨论和测试之后,我们才会整合出一个大的或向后不兼容的版本。

Promises/A+ 澄清了早期的 Promises/A 提案的行为条款,扩展了一些内容并省略了未明确说明或有问题的部分。

最后,核心的 Promises/A+ 规范不涉及如何create(创建)fulfill(履行)reject(拒绝) promises,而是选择专注于提供一种可交互的 then 方法。未来在配套规范中的工作可能会涉及这些主题。

1. 名词解释

  • 1.1. "promise" 是一个拥有符合本规范行为描述的 then 方法的 object 或者 function
  • 1.2. "thenable" 是一个定义了 then 方法的 object 或者 function
  • 1.3. "value" 是一个 JavaScript 的合法值(包括 undefinedthenable 或者promise
  • 1.4. "exception" 是一个通过 throw 语句抛出的值。
  • 1.5. "reason" 是一个值,用来表示拒绝 promise 的原因。

译者注: promisethenable 很像,区别就在于 then 方法是否严格符合本规范的定义,例如:

const obj = {
    then() {
        console.log("I'm not a promise")
    }
}

只能称 obj 是一个 thenable 而不能称其为promise,因为它的 then 方法不符合本规范的行为描述。
reason 在之后都称之为为拒因

2. 要求

2.1 Promise 状态

一个 promise 的状态必须是 pendingfulfilledrejected三种状态中的一种。

  • 2.1.1 当 promise 处于 pending 状态时:

    • 2.1.1.1 promise 可以转变为 fulfilled 或者 rejected 状态。
  • 2.1.2 当 promise 处于 fulfilled 状态时:

    • 2.1.2.1 promise 禁止转变为其他状态。
    • 2.1.2.2 必须拥有一个不可变的值
  • 2.1.3 当 promise 处于 rejected 状态时:

    • 2.1.3.1 promise 禁止转变为其他状态。
    • 2.1.3.2 必须拥有一个不可变的拒因

注意:不可变 意味着恒等(即 ===),但不意味着深度不可变。

译者注:当值或者拒因为为引用类型时,只要其引用地址相同即可。

2.2 then 方法

一个 promise 必须提供一种方法来访问其当前的最终值或者拒因。

一个 promisethen 方法接收两个参数:

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

    • 2.2.1.1 如果 onFulfilled 不是一个函数,则必须忽略掉。
    • 2.2.1.2 如果 onRejected 不是一个函数,则必须忽略掉。
  • 2.2.2 如果 onFulfilled 是一个函数:

    • 2.2.2.1 只能在 promise 的状态为 fulfilled 时被调用,并且第一个参数是 promise 的终值
    • 2.2.2.2 不能在 promise 状态为 fulfilled 之前被调用
    • 2.2.2.3 只能被调用一次
  • 2.2.3 如果 onRejected 是一个函数:

    • 2.2.3.1 只能在 promise 的状态为 rejected 时被调用,并且第一个参数是 promise 的拒因
    • 2.2.3.2 不能在 promise 状态为 rejected 之前被调用
    • 2.2.3.3 只能被调用一次
  • 2.2.4 onFulfilledonRejected 只允许执行上下文堆栈包含平台代码时才可以被调用。[3.1]

  • 2.2.5 onFulfilledonRejected 必须被作为函数调用(即没有this值)。[3.2]

  • 2.2.6 then 可以被同一个 promise 调用多次。

    • 2.2.6.1 当 promisefulfilled 状态时,所有接收到的 onFulfilled 回调函数必须按照其调用 then 的顺序执行。
    • 2.2.6.2 当 promiserejected 状态时,所有接收到的 onRejected 回调函数必须按照其调用 then 的顺序执行。
  • 2.2.7 then 方法必须返回一个 promise[3.3]

      promise2 = promise1.then(onFulfilled, onRejected);
      
    • 2.2.7.1 如果 onFulfilled 或者 onRejected 返回了一个值 x,则执行 Promise 决策程序 [[Resolve]](promise2, x)
    • 2.2.7.2 如果 onFulfilled 或者 onRejected 返回了一个异常 e,则 promise2 必须转化为 rejected 状态并以 e 作为拒因。
    • 2.2.7.3 如果 onFulfilled 不是一个函数并且 promise1fulfilled 状态,则 promise2 也必须是 fulfilled 状态并返回相同的值(promise1 返回的值)。
    • 2.2.7.4 如果 onRejected 不是一个函数并且 promise1rejected 状态,则则 promise2 也必须是 rejected 状态并返回相同的拒因(promise1 返回的拒因)。

2.3 Promise 决策程序

Promise 决策程序(promise resolution procedure) 是一个抽象的操作,将 promise 和值作为输入,用代码表示为 [[Resolve]](promise, x)。如果 x 是一个 thenable,则该程序会尝试让 promise 接受 x 的状态,我们假设 x 的行为至少像一个 promise。 否则 promise 则以 x 为最终值直接执行。

这种对 thenable 的处理使得 promise 的实现更加灵活,只要暴露出符合Promises/A+的规范的 then 方法即可。它还允许 Promises/A+兼容不合规范的实现。

运行[[Resolve]](promise, x),将执行以下几种步骤:

  • 2.3.1 如果 promisex 引用相同的对象,则拒绝执行 promise 并且以 TypeError 为拒因。

  • 2.3.2 如果 x 是一个promise,则接受它的状态[3.4]

    • 2.3.2.1 如果 x 处于 pending 状态,则 promise 则必须保持 pending 状态直到 x 变为 fulfilled 或者 rejected 状态。
    • 2.3.2.2 如果 x 处于 fulfilled 状态,则以相同的值执行 promise
    • 2.3.2.3 如果 x 处于 rejected 状态,则以相同的拒因拒绝执行 promise
  • 2.3.3 否则,如果 x 是一个对象或者函数,
    • 2.3.3.1 使用 x.then 作为 promisethen[3.5]
    • 2.3.3.2 如果获取 x.then 的过程中发生了异常 e,则以 e 作为拒因拒绝执行 promise
    • 2.3.3.3 如果 x.then 是一个函数,则以x作为 this 值调用该函数,第一个参数是 resolvePromise, 第二个参数是 rejectPromise,其中:
      • 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则执行 [[Resolve]](promise, y)
      • 2.3.3.3.2 如果 rejectPromise 以拒因 r 为参数被调用,则以 r 为拒因拒绝执行 promise
      • 2.3.3.3.3 如果 resolvePromiserejectPromise 都被调用,或者以同样的参数调用多次,则采用第一次调用并且忽略掉其他的调用。
      • 2.3.3.3.4 如果调用 then 方法中发生异常 e,
        • 2.3.3.3.4.1 如果 resolvePromise 或者 rejectPromise 被调用,则忽略它。
        • 2.3.3.3.4.2 否则以 e 为拒因拒绝执行 promise
    • 2.3.3.4 如果 x.then 不是一个函数,则以x为最终的值执行 promise
  • 2.3.4 如果 x 不是一个对象或者函数,则以x为最终的值执行 promise

如果一个 promise 在一条 由 thenable 组成的循环链中被 resolved,则会导致[[Resolve]](promise, thenable) 执行 [[Resolve]](promise, thenable),造成无限循环。我们鼓励去检测由此导致的错误,并以 TypeError 拒绝执行 promise,但这不是必须的。[3.6]

附录

3.1

这里的 platofrm code 是指引擎、环境和 promise 实现代码。实际上,这一要求确保 onFulfilledonRejected 是异步执行,并且是在 then 所被调用的那一轮的事件循环之后的新执行栈中执行。这边可以使用 setTimeout 或者 setImmediate 这类的宏任务(macro-task)实现或者使用 MutationObserverprocess.nextTick 这类的微任务(micro-task)去实现。因为promise的实现被认定为平台代码,他可能包含自身的任务队列。

3.2

也就是说,在严格模式下,this 值可能会是undefined,在非严格模式下,this 指向全局对象。

3.3

允许实现 promise2 === promise1,前提是必须实现所有要求。并且每个实现都应该记录它是否会产生及在什么条件下会产生 promise2 === promise1

3.4

只有符合当前规范的 x 才能被称为真正的 promise

3.5

首先存储对 x.then 的引用,然后测试该引用,然后调用该引用的过程应该避免对 x.then 属性的多次访问。此类预防措施对于确保访问属性的一致性非常重要,访问属性的值可能会在检索过程中发生变化。

3.6

实现不应该去设置 thenable 链的最大长度,并且去假定调用超过了设置的最大长度就是无限循环的调用。只有真正的递归调用才会抛出 TypeError,如果 thenable 链上的每一个 thenable 对象都不相同,那他就应该无限调用下去。