泛读《Promises/A+ 规范》

167 阅读9分钟

本文主要是翻译 Promises/A+ 规范,然后结合自己的理解,整理成文,方便自己以后查阅

翻译

原文地址

一份全面的、可互操作的 JavaScript promises 开放标准 — 由实现者编写,同样给实现者

一个 promise 表示一次异步操作的最终结果。与 promise 交互的主要方式是通过它的 then 方法,这个方法注册了2个回调函数,一个用于接收 promise 完成的最终值(value), 一个用于接收 promise 无法完成的原因(reason)。

这个规范详细说明了 then 方法的行为,提供了一个可互操作的基础,所有符合 Promises/A+ 规范的 promise 实现都可以依赖这个基础来提供。因此,这个规范应该是非常稳定的。尽管 Promises/A+ 组织可能偶尔会通过向后兼容的较小的更改来修订这份规范,以解决新发现的极端案例,只有经过非常认真的考虑、讨论、测试后我们才会集成大的或向后不兼容的改动。

从历史上看,Promises/A+ 使早期 Promises/A 提案 的行为语句更清晰易懂,扩展了它,让它涵盖了一些实际的行为,并省略了不明确或有问题的部分。

最后,这份核心的 Promises/A+ 规范并没有处理如何创建、完成或拒绝 promises , 而是选择专注在提供一个可互操作的 then 方法上。 未来,这份规范可能会涉及这些主题。

术语

  1. “promise” 是一个对象或者函数,有一个行为符合这份规范的 then 方法。
  2. “thenable” 是一个对象或者函数,定义了一个 then 方法。
  3. “value” 是任何合法的 JavaScript 值(包括 undefined 、 thenable、或者 promise )。
  4. "exception" 是一个使用 throw 语句作为异常被抛出的值。
  5. “reason” 是一个表明为什么这个 promise 会被拒绝的原因值。

要求

Promise 状态

一个 promise 的状态必须是这三种状态之一:pending (待定)、 fulfilled (完成) 或者 rejected (拒绝)。

  1. 当 promise 处于 pending 状态:
    1. 可以过渡到 fulfilled 或者 rejected 状态。
  2. 当 promise 处于 fulfilled 状态:
    1. 不可以过渡到任何状态。
    2. 必须有一个值(value),这个值不可以变。
  3. 当 promise 处于 rejected 状态:
    1. 不可以过渡到任何状态。
    2. 必须含有一个原因 (reason),这个原因不可以变。

这里的 “不可以变” 意味着不可变的标识(即,===),但并不意味着更深度的不可变。

then 方法

一个 promise 必须提供一个 then 方法来获取它当前或者最终的值(value)或原因(reason)。

一个 promise 的 then 方法接收 2 个参数:

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. onFulfilled 或者 onRejected 不能被调用直到 执行上下文 堆栈只包含平台代码。[注:1]

  5. onFulfilled 或者 onRejected 必须作为函数被调用(没有 this 值)。[注:2]

  6. then 在同一个 promise 中可以被调用多次

    1. 如果/当 promise 是完成 (fulfilled) 状态时,所有的 onFulfilled 回调必须按照它们在 then 上创建的顺序被调用。
    2. 如果/当 promise 是拒绝 (rejected) 状态时,所有的 onRejected 回调必须按照它们在 then 上创建的顺序被调用。
  7. then 必须返回一个 promise [注:3]

     promise2 = promise1.then(onFulfilled, onRejected);
    
    1. 如果 onFulfilled 或者 onRejected 返回了一个值 x, 执行 Promise 解决程序 [[Resolve]](promise2,x)
    2. 如果 onFulfilled 或者 onRejected 抛出异常 e, promise2 必须是 rejected 状态,e 是它的 reason 值。
    3. 如果 onFulfilled 不是一个方法,并且 promise1 是 fulfilled 状态, promise2 状态也必须是 fulfilled,并且 value 值和 promise1 相同。
    4. 如果 onRejected 不是一个方法,并且 promise1 是 rejected 状态,promise2 状态也必须是 rejected,并且 reason 值和 promise1 相同。

Promise 解决程序

promise 解决程序 是一个输入 promise 和 value 值的抽象操作,我们将其表示为 [[Resolve]](promise, x)。如果 xthenable, 就假定 x 的行为至少有点像 promise,它将会尝试让 promise 采用 x 的状态。否则, 它将用 x 作为 value 来完成(fulfill) 这个 promise

对 thenables 的这种处理允许 promise 互操作来实现,只要他们暴露了符合 Promises/A+ then 方法规范。它也允许 Promises/A+ 的实现融入不符合规范但拥有合理的 then 方法。

执行 [[Resolve]](promise, x), 表现为以下步骤:

  1. 如果 promisex 指向的是同一个对象, 以 TypeError 作为原因 (reason) 拒绝这个 promise
  2. 如果 x 是一个 promise, 采用它的状态 [注:4]
    1. 如果 xpending 状态, 那么 promise 也需要保持 pending 状态直到 x 变为 fulfilled 或者 rejected 状态。
    2. 如果/当 xfulfilled 状态时,用相同的值使 promise 变为 fulfilled 状态。
    3. 如果/当 xrejected 状态时, 用相同的原因使 promise 变为 rejected 状态。
  3. 除此之外, 如果 x 是一个对象或者函数
    1. then 赋值为 x.then。 [注:5]
    2. 如果检索属性 x.then 发生错误 e, 用 e 作为原因 (reason) 拒绝 promise
    3. 如果 then 是一个函数,将 x 作为 this 调用它,第一个参数 resolvePromise,第二个参数 rejectPromise,其中:
      1. 如果/当 resolvePromise 以 value y 值被调用时,运行 [[Resolve]](promise, y)
      2. 如果/当 rejectPromise 以 reason r 值被调用时,用 r 值作为原因 (reason) 拒绝 promise
      3. 如果 resolvePromiserejectPromise 都被调用,或者多次调用相同的参数,第一次调用被程序执行,后续的调用将会被忽略。
      4. 如果调用 then 抛出异常 e
        1. 如果 resolvePromiserejectPromise 已经被调用了,则忽略它。
        2. 否则,以 e 作为原因 (reason) 拒绝 promise
    4. 如果 then 不是对象或者函数,以 x 完成 promise
  4. 如果 x 不是对象或者函数, 以 x 作为 value 值完成 promise

如果一个 promise 是用一个 thenable 来解决的,这个 thenable 参与了一个循环的 thenable 链,这样递归的性质 [[Resolve]](promise, thenable) 最终会导致 [[Resolve]](promise, thenable) 被再次调用,按照上面的算法会导致无限递归。鼓励实现检测这种递归并以 TypeError 作为 reason 拒绝 promise,但不是必须的。[注:6]

  1. 这里的 平台代码 指引擎、环境和 promise 实现代码。在实践中,这要求确保 onFulfilledonRejected 异步地执行,当 then 被调用,进行下一个事件循环,使用新的调用栈。这可以通过 “宏任务”机制 比如 setTimeout 或者 setImmediate,"微任务"机制 比如 MutationObserver 或者 process.nextTick 来实现。由于 promise 的实现被认为是平台代码,在自身处理程序被调用时可能已经包含一个任务调度队列。
  2. 也就是说,在严格模式下,内部的 this 会是 undefined,在非严格模式下,this 将会是全局对象。
  3. promise1 === promise2 提供的实现满足所有要求,则可以被允许。每个实现都应该记录它是否可以产生 promise1 === promise2 以及在什么条件下产生。
  4. 一般来说,只有 x 来自当前的实现,它才会被知道是一个真正的 promise。该条款允许特定实现的使用,这里的使用意味着采用已知符合规范的 promises 的状态。
  5. 这个程序首先要存储 x.then 的引用,然后测试这个引用,然后调用这个引用,避免多次去获取 x.then 属性。这些预防措施对于确保访问器属性的一致性很重要,访问器属性的值可能会在检索之间发生变化。
  6. 实现不应该对 thenable 链的深度做任何的限制,并假设超出任何限制递归将是无限的。只有真正的死循环才会导致 TypeError; 如果遇到无限循环的不同的 thenable 调用链,递归永远是正确的行为。

=======================================================================================

总结

这是 Promise 的实现规范,主要专注在 then 方法上。

Promise 有三种状态:pending 、fulfilled 、 rejected,并且状态是不可逆的,只能由 pending -> fulfilled 或者 pending -> rejected。

Promise 的 then 方法接收2个参数,第一个 onFulfilled,用于处理完成时的结果,第二个 onRejected , 用于处理拒绝的结果,这两个参数都应该是函数,不是函数会被忽略。

onFulfilledonRejected 必须是作为下个事件循环调用(等第一批同步任务队列执行完再执行,可以用微任务实现,也可以用宏任务实现),并且只能在 pending 状态发生改变后被调用一次。then 必须作为函数被调用,不能作为对象属性的方法被调用,then 可以被调用多次,但需要保证调用的顺序与定义的顺序一致。

then 返回的必须是 promise,由于 onFulfilled 或者 onRejected 的返回值 x 不一定是一个 promise,所以需要一个处理程序来解决保证它们返回的是一个 promise ,这个程序是 [[Resolve]](promise, x)

[[Resolve]](promise, x) 的处理逻辑是判断 x 是不是一个 promise, 如果是,使用 x 的状态作为 promise 的状态, 如果不是,还需要看 x 有没有 then 方法, 如果有,将 then 赋值为 x.then,这其中还需要根据这个 then 方法再做一层判断,then的调用或者获取出现异常就抛出异常,返回 e 为错误原因的 promise,如果可以正常符合 promise 规范的调用,并第一个回调函数被调用同样返回一个 y 值时,再递归走一遍这样的处理程序,如果调用出现异常,异常值作为原因返回这个promise。 如果没有,则返回用 x 作为完成值的 promise。

以上是大概的一个总结,细节会存在一些遗漏,主要是帮助快速理解整个规范流程。