Promises/A+规范翻译 & 学习

原文翻译 & 学习注释

原文链接

1. 术语

1.1 "promise"是一个拥有 then 方法的函数或者对象,它的行为符合本规范。
1.2 "thenable"是一个定义了 then 方法的函数或者对象。
1.3 "value"是一个合法的 JavaScript 值(该值的类型包括 undefined、thenable 或者 promise)。
1.4 "exception"是一个使用 throw 语句抛出的值。
1.5 "reason"是用来表示一个 promise 失败原因的值。

2. 要求

2.1 Promise状态

promise 必须处于以下三种状态之一:pending, fulfilled 或者 rejected.
    2.1.1 当处于 pending 状态时,promise:
        2.1.1.1 可能转换为 fulfilled 状态或者 rejected 状态。
    2.1.2 当处于 fulfilled 状态时,promise:
        2.1.2.1 不能转换为其他状态。
        2.1.2.2. 必须有一个 value,该 value 不可被改变。
    2.1.3 当处于 rejected 状态时,promise:
        2.1.3.1 不能转换为其他状态。
        2.1.3.2. 必须有一个 reason,该 reason 不可被改变。
在此,“不可被改变”意味着不变的特征(即===),但是并不表示 "deep immutability"
"deep immutability" 类似于 const 作用于引用类型时的情况:

const arr = [1];
arr = [2]; //  Uncaught TypeError: Assignment to constant variable.
arr[0] = 2;
console.log(arr); // [ 2 ] 

promise 举例:

var value = [1];
var p = Promise.resolve(value);
p.then(a => console.log(a));                  // [1]
p.then(a => console.log(a === value));        // true

// 对value引用的修改仅在当前then函数的块级作用域内有效。
p.then(a => { a = [2]; console.log(a);});     // [2]
p.then(a => console.log(a));                  // [1]

// 可以修改value中每一项的值。
p.then(a => { a[0] = 2; console.log(a);});    // [2]
p.then(a => console.log(a));                  // [2]
p.then(a => console.log(a === value));        // true
2.2 then方法

promise 必须提供一个 then 方法来访问它当前或者最终的 value / reason。
promise 的 then 方法接受两个参数:

promise.then(onFulfilled, onRejected)

    2.2.1 onFulfilled 和 onRejected 都是可选的参数:
        2.2.1.1 如果 onFulfilled 不是一个函数,它应该被忽略。
        2.2.1.2 如果 onRejected 不是一个函数,它应该被忽略。
    2.2.2 如果 onFulfilled 是一个函数:
        2.2.2.1 该函数只能在 promise 的状态变为 fulfilled 之后被调用,并且它的第一个参数是 promise 的 value。
        2.2.2.2 在 promise 的状态变为 fulfilled 之前,该函数不能被调用。
        2.2.2.3 该函数不能被多次调用。
    2.2.3 如果 onRejected 是一个函数:
        2.2.3.1 该函数只能在 promise 的状态变为 rejected 之后被调用,并且它的第一个参数是 promise 的 reason。
        2.2.3.2 在 promise 的状态变为 rejected 之前,该函数不能被调用。
        2.2.3.3 该函数不能被多次调用。
    2.2.4 onFulfilled 或者 onRejected 在执行上下文堆栈仅包括"platform code"之前不得被调用。("platform code" 指的是引擎、环境以及 promise 的实现代码。实际上,这一要求确保 onFulfilled 和 onRejected 异步执行,并且在事件循环结束后返回到 then 函数被调用的地方,重新使用新的堆栈。这可以通过宏任务机制(例如 setTimeout 或者 setImmediate)或微任务机制(例如 MutationObserver 或者 process.nextTick)实现。 由于 promise 的实现被看做 platform code,它可能自身包含一个任务调度队列或者“蹦床”,在其中调用处理程序)。
    2.2.5 onFulfilled 和 onRejected 应当被作为函数调用(即没有 this 值)。
这里的没有 this 值指的是,在 strict 模式下,this值为 undefined,在 sloppy 模式下,this 值为全局对象。
    2.2.6 then 可以在同一个 promise 中被多次调用。
        2.2.6.1 当 promise 变为 fulfilled 状态时,所有各自的 onFulfilled 回调函数应当按照它们调用 then 函数时的原始顺序被执行。
        2.2.6.2 当 promise 变为 rejected 状态时,所有各自的 onRejected 回调函数应当按照它们调用 then 函数时的原始顺序被执行。
    2.2.7 then 函数必须返回一个 promise。
假如代码实现满足了所有的要求,那么应当允许 promise2 === promise1。每一个实现需要文档记录它是否允许 promise2 === promise1 以及在什么条件下才允许这种结果。

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 状态,reason 是 e。
        2.2.7.3 如果 onFulfilled 不是一个函数并且 promise1 处于 fulfilled 状态,promise2 必须是 fulfilled 状态,并且 value 和 promise1 相同。
        2.2.7.4 如果 onRejected 不是一个函数并且 promise1 处于 rejected 状态, promise2 必须是 rejected 状态,并且 reason 和 promise1 相同。

2.3 Promise 解析流程

promise 解析过程是以一个 promise 和一个 value 作为输入参数的抽象操作,我们将其表示为[[Resolve]](promise, x)。如果 x 是一个 thenable,它试图使 promise 采用 x 的状态,前提是 x 的行为至少有点像 promise 。否则,使用值 x 执行 promise。
这种对 thenable 的处理允许 promise 实现交互操作,只要它们暴露符合 Promises/A+ 规范的 then 方法。它还允许 Promises/A+ 规范实现用合理的 then 方法“同化”不符合要求的实现。
要运行[[Resolve]](promise, x),请执行接下来的步骤:
    2.3.1 如果 promise 和 x 指向同样的对象,使用 TypeError 作为 reason 拒绝该 promise。
    2.3.2 如果 x 是一个 promise, 采纳它的状态(通常来说,只有当 x 来自当前的实现时,才可以确定 x 是一个真正的 promise。本条款允许使用特定的实施方法来采用符合已知规范的 promise 的状态):
        2.3.2.1 如果 x 处于 pending 状态, promise 必须保持 pending 状态直到 x 变为 fulfilled 状态或者 rejected状态.
        2.3.2.2 如果 x 处于 fulfilled 状态, 用与 x 相同的 value 值执行该 promise。
        2.3.2.3 如果 x 处于 rejected 状态, 用与 x 相同的 reason 值拒绝该 promise。
    2.3.3 否则,如果 x 是一个对象或者函数,
        2.3.3.1 将 x.then 赋值给 then(首先存储对 x.then 的引用,然后测试该引用,之后调用该引用。这个过程避免了对 x.then 属性的多次访问。这些预防措施对于确保访问器属性的一致性非常重要,因为访问器属性的值在两次检索之间可能会发生变化):
        2.3.3.2 如果检索属性 x.then 导致抛出了一个异常 e,使用 e 作为 reason 拒绝该 promise。
        2.3.3.3 如果 then 是一个函数,将 x 作为 then 函数的 this,第一个参数叫 resolvePromise,第二个参数叫 rejectPromise:
            2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,运行[[Resolve]](promise, y)。
            2.3.3.3.2 如果 rejectPromise 以 reason r 为参数被调用,使用 r 拒绝该 promise。
            2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都被调用了,或者被相同的参数调用了多次,则优先接受首次调用,并忽略剩下的调用。
            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 如果 then 不是一个函数,使用 x 执行该 promise。
    2.3.4 如果 x 不是对象或者函数,使用 x 执行该 promise。
如果一个 promise 被一个处于 thenable 循环链中的 thenable 解决(例如[[Resolve]](promise, thenable) 的递归性质导致其自身再次被调用),遵循上述的算法会导致无限递归。(我们)鼓励(但不是要求)实现检测这种递归,并以信息拼写错误作为 reason 拒绝 promise(实现不应该对 thenable 链的深度设置任意大小的限制,并且假定超过该限度的递归将会是无限的。只有真正的循环应该导致 TypeError,如果碰巧遇见了一个包含不同 thenable 的无限链,那么永远递归是正确的行为)。

参考资料

  1. Promises/A+
  2. Promise 源码学习】第四篇 - 翻译并理解 Promise A+ 规范
  3. What is deep immutability?

知白守黑
1 声望0 粉丝