原文:Promises/A+
一个 Promise 代表一个异步操作最终结果。与 promise 进行交互的主要方式是通过 then
方法,then
方法是通过注册回调方法去接受一个 promise 的最终值或者没有完成的原因。
Promises/A+ 规范 详细说明了 then
方法的执行过程, 给所有符合 Promises/A+ 规范的 Promise 实现都提供了可以依赖的参照基础。因此这个规范是非常稳定的。尽管 Promises/A+ 组织可能会偶尔对该规范进行一些小的、向后兼容的改变,去处理一些新发现的极端情况。只有在进行了谨慎的考虑
讨论和测试之后,我们才会去进行大的或者不兼容的变更。
从历史上来说,Promises/A+ 把更早的 Promises/A 提案明确成了行为条款。一方面扩展了原规范 覆盖的实际行为,另一方面删减了原规范的一些没有详细说明或者有问题的部分。
最后,Promises/A+ 规范的核心并没有处理如何去创建,完成,或者拒绝一个 promise,而是聚焦于提供一个通用的 then
方法。上述的对于 promise 的操作方法在未来的其他规范中可能会提及。
1、术语
promise
promise 是一个拥有 then
方法的对象,并且其行为符合本规范;
thenable
是一个定义了 then
方法的对象或者函数(可以译为“拥有 then
方法”);
value
value 是任何一个合法的 JavaScript 值(包括 undefined
,thenable 和 promise);
exception
异常,是 throw
抛出的一个值;
reason
表示一个 promise 被拒绝的原因;
2、要求
2.1、Promise 状态
一个 promise 必须是以下三种状态之一:pending,fulfilled, or rejected。
- 当一个 promise 是 pending 状态时
- 可以过渡到 fulfilled 状态或者 rejected 状态。
- 当一个 promise 是 fulfilled 状态时
- 不能过渡到任何状态
- 必须拥有一个不可变的 value
- 当一个 promise 是 rejected 状态
- 不能过渡到任何状态
- 必须拥有一个不可变的 reason
这里的不可变只是意味着恒等(可用===
判断),而不是深层次的不可变。(比如 value 或者 reason 是一个 对象时,只要求引用地址相等,其属性值可以被更改)
2.1、Then
方法
一个 promise 必须提供一个 then
方法去接收它当前值, value 或者 reason。
一个 promise 的 then
方法接收两个参数:promise.then(onFulfilled,onRejected)
-
onFulfilled
和onRejected
都是可选参数:- 如果
onFulfilled
不是一个函数,会被忽略 - 如果
onRejected
不是一个函数,会被忽略
- 如果
-
如果
onFulfilled
是一个函数:- 必须在 promise 过渡到 fulfilled 状态后调用,其第一个参数为 promise 的 value
- 不能在过渡到 fulfilled 状态之前调用
- 最多只能被调用一次
-
如果
onRejected
是一个函数:- 必须在 promise 过渡到 rejected 状态后调用,其第一个参数为 promise 的 reason
- 不能在 过渡到 rejected 状态之前调用
- 最多只能被调用一次
-
调用时机:
onFulfilled
和onRejected
只有在执行环境堆栈仅包含平台代码时才可以被调用。[^1] -
调用要求:
onFulfilled
和onRejected
只能以函数的形式被调用(没有this
值)[^2] -
多次调用:
then
方法可以被一个 promise 多次调用:- 当 promise 过渡到 fulfilled 状态时,所有的
onFulfilled
须按照其注册顺序依次执行 - 当 promise 过渡到 rejected 状态时,所有的
onRejected
须按照其注册顺序依次执行
- 当 promise 过渡到 fulfilled 状态时,所有的
-
返回:
then
方法必须返回一个 promise。[^3]promise2 = promise1.then(onFulfilled, onRejected);
- 如果
onFulfilled
或者onRejected
返回一个值x
,则运行 Promise 解决程序[[Resolve]](promise2, x)
- 如果
onFulfilled
或者onRejected
抛出一个异常e
,则promise2
必须过渡到 rejected 状态,并以e
为 reason 参数 - 如果
onFulfilled
不是一个函数,并且promise1
已过渡到 fulfilled 状态,那么promise2
必须也过渡到 fulfilled 状态并以promise1
的 value 作为 value - 如果
onRejected
不是一个函数,并且promise
已拒绝执行,那么promise2
必须过渡到 rejected 状态并以promise1
的 reason 作为 reason
- 如果
2.2、Promise 的解决程序
promise 的解决程序是一个抽象的操作。它需要输入一个 promise 和 一个值,我们表示为 [[Resolve]](promise, x)
,如果 x
有 then
方法且看上去像 promise
,解决程序会尝试让这个 promise
接收 x
的状态。否则就用x
的值来执行。
这种 thenable
的特性使得 promise 的实现更具有通用性, 只要他暴露一个符合 Promises/A+ 规范的 then
方法。这使得那些符合 Promises/A+ 规范的实现可以与不符合规范但可用的实现可以良好的共存。
运行 [[Resolve]](promise, x)
需要执行以下步骤:
1、如果 promise
和 x
指向同一个对象,以 TypeError
为 reason 拒绝执行 promise
2、如果 x
是 promise,则使 promise
接受 x
的状态:[^4]
- 2.1、如果
x
处于 pending 状态,promise
需保持为等待状态直至x
被执行或拒绝 - 2.2、如果
x
处于 resolved 状态,用相同的 value 执行promise
- 2.3、如果
x
处于 rejected 状态,用相同的 reason 执行promise
3、如果 x
是对象或者函数:
- 3.1、把
x.then
赋值给then
[^5] - 3.2、如果取
x.then
的值时抛出错误e
,拒绝执行这个promise
并以e
作为 reason - 3.3、如果
then
是函数,将x
作为函数的作用域this
调用then
。传递两个回调函数作为参数,第一个参数叫resolvePromise
,第二个参数叫rejectPromise
:- 3.3.1、如果
resolvePromise
以y
为 value 被调用,执行[[Resolve]](promise, y)
- 3.3.2、如果
rejectPromise
以r
为 reason 被调用, 拒绝promise
并以r
为 reason - 3.3.3、如果
resolvePromise
和rejectPromise
都被调用,或者被同样的参数调用了多次,则优先采用首次调用,并忽略其他的调用 - 3.3.4、如果调用
then
方法抛出了异常e
:- 如果
resolvePromise
或者rejectPromise
已经被调用,忽略异常 - 如果没有被调用,拒绝执行
promise
并以e
为 reason
- 如果
- 3.3.1、如果
- 3.4、如果
then
不是一个函数,成功执行promise
并以x
为 value
4、如果 x
不是一个对象或者函数, 成功执行 promise
并以 x
为 value
如果一个 promise 被一个循环的 thenable
链中的对象成功执行,[[Resolve]](promise, thenable)
的递归性质又使得其再次调用,根据上述的算法将会陷入无限递归之中。算法虽然没有要求,但也鼓励实现者去检测这样的递归。如果检测到,则以一个可识别的 TypeError
为 reason 来决绝执行 promise
。[^6]
3、注释
[^1]:这里的【平台代码】指的是引擎、环境以及 promise 的实现代码。实践中,这个要求确保了 onFulfilled
和 onRejected
异步执行,并且是在 then
方法被调用的那一轮事件循环之后的新执行栈中执行。这个可以使用像 setTimeout
或者 setImmediate
这样的“宏任务”机制实现,也可以用像 MutationObserver
或者 process.nextTick
这样的“微任务”机制 来实现。由于 promise 的实现本身就是平台代码(JavaScript),所以它本身在处理程序时可能已经包含了一个任务调度队列
[^2]: 在严格模式下,函数中this
会是 undefined
。在宽松模式下,this
会是全局对象。
[^3]: 代码实现在满足所有要求的情况下,允许 promise2===promise1
。每一个实现都要文档说明是否允许 promise2===promise1
以及如果允许的话,是在什么情况下允许。
[^4]: 通常,如果 x
符合当前实现,我们才认为是真正的 promise。这一规则允许那些特例实现可以接受符合已知规范的 promise 的状态。
[^5]: 这里我们先存储了 x.then
的引用,然后测试并后调用这个引用,避免多次取用 x.then
属性。这种预防措施确保了该属性的一致性,因为其值可能在检索调用时改变。
[^6]: 实现不应该对 thenable 链的深度设限,并假定超出设定限度的递归就是无限循环。只有真正的循环递归才会导致 TypeError
异常; 如果一条无限长的链上 thenable 均不同,那么递归下去永远是正确的行为。