PromiseA+规范
了解Promise首先我们要清楚Promise规范的内容,规范规定了Promise的行为和调用方式。这里是规范原文。下面是翻译总结:
一个Promise主要代表了一个异步操作的最终结果。与Promise交互的主要方式是通过promise实例的then
方法注册回调函数。回调函数分为两种,一种接受异步操作成功时的回调,接受异步操作结果值作为参数;第二种是异步操作失败时的回调,接受异步操作失败原因为参数。规范主要是详细描述了then
方法的实现细节。
主要要求如下:
状态
一个promise必须处于这3种状态之中:pending
、fullfilled
、rejected
pending
: 可以转换到fullfilled
或者rejected
状态fullfilled
: 不能转换到任何状态且有一个不可变的结果值rejected
: 不能转换到任何状态且有一个不可变的失败原因
then
方法
一个promise
必须有一个then
方法来接受异步操作的结果值或者失败原因
一个promise
的then应该接收两个参数,成功回调函数和失败回调函数:
1 | promise.then(onFulfilled, onRejected) |
onFulfilled和onRejected都是可选的:
- 如果onFulfilled不是一个函数,它必须被忽略
- 如果onRejected不是一个函数,它必须被忽略
如果onFulfilled是函数:
- 它必须在
promise
状态变为fullfilled
之后执行,且接受promise
的结果值作为第一个参数 - 它在
promise
状态变为fullfilled
之前不能执行 - 它最多被执行一次
- 它必须在
如果onRejected是函数:
- 它必须在
promise
状态变为rejected
之后执行,且接受promise
的失败原因作为第一个参数 - 它在
promise
状态变为rejected
之前不能执行 - 它最多被执行一次
- 它必须在
onFulfilled
或者onRejected
直到执行环境堆栈只包含平台代码时才可以执行onFulfilled
或者onRejected
必须作为一个独立的函数被调用。(不能作为对象属性执行或者其他指定this的执行方式,比如call和apply)then
方法可以在同一个promise
上多次调用:- 当
promise
状态变为fullfilled
时,通过then注册的所有onFulfilled
回调按照注册顺序依次执行 - 当
promise
状态变为rejected
时,通过then注册的所有onRejected
回调按照注册顺序依次执行
- 当
then
方法必须返回一个promise:1
promise2 = promise1.then(onFulfilled, onRejected);
- 如果
onFulfilled
或者onRejected
返回x
, 则运行Pomise Resolution Procedure[[Resolve]](promise2, x)
- 如果
onFulfilled
或者onRejected
抛出一个异常e
,promise2
必须以e
作为失败原因变成rejected
状态 - 如果
onFulfilled
不是一个函数,且promise1
为fullfilled
状态,则promise2
也转变为fullfilled
,且结果值和promise1
一样 - 如果
onRejected
不是一个函数,且promise1
为rejected
状态,则promise2
也转变为rejected
,且失败原因和promise1
一样
- 如果
The Promise Resolution Procedure
promise resolution procedure
表示为[[Resolve]](promise, x)
,是一个接受一个promise和一个值作为参数的抽象操作。如果一个函数是一个thenable
(thenable
表示有then
方法的对象或者函数),则它试图使promise
采用x
的状态。这里对于thenable
的处理使Promise更加通用,能够兼容之前并不符合规范但是有合理then
方法的异步实现。
为了运行[[Resolve]](promise, x)
,需要执行以下步骤:
- 如果
promise
和x
指向同一个对象,则以TypeError
为失败原因将promise
转变为rejected
状态 - 如果
x
是一个promise,则promise
采用x
的状态以及相应的结果值或者原因 - 如果
x
是一个对象或者函数Let
定义then
,值为x.then
- 如果在获取
x.then
的过程中抛出异常e
,promise
以e
作为失败原因变成rejected
状态 - 如果
then
是一个函数,以x
作为它的this
调用它,第一个参数为resolvePromise
,第二个参数为rejectPromise
:- 如果
resolvePromise
以参数y
被调用的话,则执行[[Resolve]](promise, y)
- 如果
rejectPromise
以参数r
被调用的话,则以r
作为失败原因使promise
变成rejected
状态 - 如果
resolvePromise
和rejectPromise
都被调用了,或者以同样的参数被调用多次,则以第一次调用为准,忽略之后的调用 - 如果调用
then
时抛出一个异常e
:- 如果
resolvePromise
或rejectPromise
被调用了,则忽略 - 否则,
promise
以e
作为失败原因变成rejected
状态
- 如果
- 如果
- 如果
then
不是一个函数,则promise
以x
为结果值变为fullfilled
状态
- 如果
x
不是一个对象或者函数,则promise
以x
为结果值变为fullfilled
状态
Promise实现
我们可以看出标准还是挺复杂的,我们就一步一步,从简到繁地实现Promise。标准中并没有规定Promise如何创建,如何完成状态转换,这些我们可以参考ES6的promise用法:
1 | const promise1 = new Promise((resolve, reject) => { |
promise的状态
Promise以类的方式出现,构造函数接受一个函数fn
作为参数,fn
有两个参数:
resolve
,使promise从pending
状态转换为fullfilled
状态,resolve(value)
的参数value为promise的结果值;reject
,使promise从pending
状态转换为rejected
状态,reject(reason)
的参数reason为promise的失败原因。
我们第一版可以先从状态写起:
1 | const PENDING = 'pending'; |
try…catch…是为了捕获用户自定义函数fn
的错误,如果fn
执行出错,则promise会进入rejected
状态。
then函数的两个函数参数的执行
接下来我们考虑then
函数,then
函数比较复杂,我们先考虑then(onFulfilled, onRejected)
函数中两个函数参数的执行问题,把then
函数的返回值问题放在后面。
then
函数中两个函数参数的执行情况分为3种:
promise处于
pending
状态,那在then函数中不执行两个函数参数,等到fn
中的异步操作有结果了,再根据成功或者失败的结果去执行promise处于
fullfilled
状态,则直接执行then
函数的第一个函数参数onFulfilled
promise处于
rejected
状态,则直接执行then
函数的第二个函数参数onRejected
1 | class Promise { |
以上代码很简单,我们借助fullFilledCallbacks
和rejectedCallbacks
两个数组存储在promise状态为发生转变之前通过then
方法注册的onFulfilled
和onRejected
回调。
then的返回值
由PromiseA+规范可以,then
方法必须返回一个promise:
1 | promise2 = promise1.then(onFulfilled, onRejected); |
且promise2的状态由onFullfilled
或者onRejected
的返回值决定,如何进行转换则由promise resolution procedure
方法来实现。我们先不考虑promise resolution procedure
,根据规范实现如下:
1 | then(onFulfilled, onRejected){ |
接下来我们来实现promiseResolution
函数,如果已经忘了可以去复习一下promiseResolution
的规范。promiseResolution
的主要任务是根据onFulFilled
或者onRejected
的返回值x
来决定promise2
的状态转变。
1 | promiseResolution(promise, x, resolve, reject){ |
异步问题
规范中有一条时,onFulfilled
或者onRejected
直到执行环境堆栈只包含平台代码时才可以执行。规范给出的注释是:平台代码是指引擎、执行环境和Promise实现代码,就是说js主栈中不能有其他代码,这就是要求我们异步执行onFulfilled
或者onRejected
;我们可以用宏任务(setTimeout 或者 setImmediate)或者微任务(MutationObserver or process.nextTick)机制来实现。
我们知道,ES6的promise的then方法回调异步执行的机制是微任务。在浏览器环境我们可以使用MutationObserver来模拟微任务机制,如果浏览器不支持MutationObserver
,我们回退到setTimeout使用宏任务实现。
1 | function isNative(fn){ |
我们使用异步机制来重写then
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51then(onFulfilled, onRejected){
let _resolve;
let _reject;
const promise2 = new Promise((resolve, reject) => {
_resolve = resolve;
_reject = reject;
});
// 如果`onFulfilled`不是一个函数,且`promise1`为`fullfilled`状态,则`promise2`也转变为`fullfilled`,且结果值和`promise1`一样
if(typeof onFullfilled !== 'function'){
onFullfilled = (value) => value;
}
// 如果`onRejected`不是一个函数,且`promise1`为`rejected`状态,则`promise2`也转变为`rejected`,且失败原因和`promise1`一样
if(typeof onRejected !== 'function'){
onRejected = (reason) => throw new Error(reason);
}
// 如果`onFulfilled`或者`onRejected`返回`x`, 则运行Pomise Resolution Procedure`[[Resolve]](promise2, x)`
const excuteFullfilled = () => {
try{
const x = onFulfilled(this.value);
this.promiseResolution(promise2, x, _resolve, _reject);
}catch(e){
_reject(e);
}
}
const excuteRejected = () => {
try{
const x = onRejected(this.reason);
this.promiseResolution(promise2, x, _resolve, _reject);
}catch(e){
_reject(e);
}
}
if (this.status === 'fullfilled'){
registerAysn(excuteFullfilled);
} else if (this.status === 'rejected'){
registerAysn(excuteRejected);
} else {
this.fullFilledCallbacks.push(() => {
registerAysn(excuteFullfilled);
});
this.rejectedCallbacks.push(() => {
registerAysn(excuteRejected);
});
}
return promise2;
}
目前为止我们实现了Promise的基本功能。
Promise静态方法和其他实例方法
1 | class Promise{ |