Promise解析和实现

PromiseA+规范

了解Promise首先我们要清楚Promise规范的内容,规范规定了Promise的行为和调用方式。这里是规范原文。下面是翻译总结:

一个Promise主要代表了一个异步操作的最终结果。与Promise交互的主要方式是通过promise实例的then方法注册回调函数。回调函数分为两种,一种接受异步操作成功时的回调,接受异步操作结果值作为参数;第二种是异步操作失败时的回调,接受异步操作失败原因为参数。规范主要是详细描述了then方法的实现细节。

主要要求如下:

状态

一个promise必须处于这3种状态之中:pendingfullfilledrejected

  • 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不是一个函数,且promise1fullfilled状态,则promise2也转变为fullfilled,且结果值和promise1一样
    • 如果onRejected不是一个函数,且promise1rejected状态,则promise2也转变为rejected,且失败原因和promise1一样

The Promise Resolution Procedure

promise resolution procedure表示为[[Resolve]](promise, x),是一个接受一个promise和一个值作为参数的抽象操作。如果一个函数是一个thenablethenable表示有then方法的对象或者函数),则它试图使promise采用x的状态。这里对于thenable的处理使Promise更加通用,能够兼容之前并不符合规范但是有合理then方法的异步实现。

为了运行[[Resolve]](promise, x),需要执行以下步骤:

  • 如果promisex指向同一个对象,则以TypeError为失败原因将promise转变为rejected状态
  • 如果x是一个promise,则promise采用x的状态以及相应的结果值或者原因
  • 如果x是一个对象或者函数
    • Let定义then,值为x.then
    • 如果在获取x.then的过程中抛出异常e,promisee作为失败原因变成rejected状态
    • 如果then是一个函数,以x作为它的this调用它,第一个参数为resolvePromise,第二个参数为rejectPromise:
      • 如果resolvePromise以参数y被调用的话,则执行[[Resolve]](promise, y)
      • 如果rejectPromise以参数r被调用的话,则以r作为失败原因使promise变成rejected状态
      • 如果resolvePromiserejectPromise都被调用了,或者以同样的参数被调用多次,则以第一次调用为准,忽略之后的调用
      • 如果调用then时抛出一个异常e:
        • 如果resolvePromiserejectPromise被调用了,则忽略
        • 否则,promisee作为失败原因变成rejected状态
    • 如果then不是一个函数,则promisex为结果值变为fullfilled状态
  • 如果x不是一个对象或者函数,则promisex为结果值变为fullfilled状态

Promise实现

我们可以看出标准还是挺复杂的,我们就一步一步,从简到繁地实现Promise。标准中并没有规定Promise如何创建,如何完成状态转换,这些我们可以参考ES6的promise用法:

1
2
3
4
const promise1 = new Promise((resolve, reject) => {

});
promise1.then((value) => {}, (reason)=>{})

promise的状态

Promise以类的方式出现,构造函数接受一个函数fn作为参数,fn有两个参数:

  • resolve,使promise从pending状态转换为fullfilled状态,resolve(value)的参数value为promise的结果值;
  • reject,使promise从pending状态转换为rejected状态,reject(reason)的参数reason为promise的失败原因。

我们第一版可以先从状态写起:

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
const PENDING = 'pending';
const FULLFILLED = 'fullfilled';
const REJECTED = 'rejected';
class Promise {
constructor(fn) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
if(this.status === PENDING){
this.status = FULLFILLED;
this.value = value;
}
}
const reject = (reason) => {
if(this.status === PENDING){
this.status = REJECTED;
this.reason = reason;
}
}
try{
fn(resolve, reject);
}catch(e){
reject(e);
}
}
}

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
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
class Promise {
constructor(fn) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.fullFilledCallbacks = []; // 存储状态转变之前的注册的onFulfilled
this.rejectedCallbacks = []; // 存储状态转变之前的注册的onRejected
const resolve = (value) => {
if(this.status === PENDING){
this.status = FULLFILLED;
this.value = value;
this.fullFilledCallbacks.forEach(callback => callback(value));
}
}
const reject = (reason) => {
if(this.status === PENDING){
this.status = REJECTED;
this.reason = reason;
this.rejectedCallbacks.forEach(callback => callback(reason));
}
}
try{
fn(resolve, reject);
}catch(e){
reject(e);
}
}

then(onFulfilled, onRejected){
if(typeof onFullfilled !== 'function'){
onFullfilled = () => {};
}
if(typeof onRejected !== 'function'){
onRejected = () => {};
}

if (this.status === 'fullfilled'){
onFulfilled(this.value);
} else if (this.status === 'rejected'){
onRejected(this.reason);
} else {
this.fullFilledCallbacks.push(onFulfilled);
this.rejectedCallbacks.push(onRejected);
}
}
}

以上代码很简单,我们借助fullFilledCallbacksrejectedCallbacks两个数组存储在promise状态为发生转变之前通过then方法注册的onFulfilledonRejected回调。

then的返回值

由PromiseA+规范可以,then方法必须返回一个promise:

1
promise2 = promise1.then(onFulfilled, onRejected);

且promise2的状态由onFullfilled或者onRejected的返回值决定,如何进行转换则由promise resolution procedure方法来实现。我们先不考虑promise resolution procedure,根据规范实现如下:

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
51
52
53
54
55
56
57
58
59
60
then(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 = () => {
const x = onFulfilled(this.value);
this.promiseResolution(promise2, x, _resolve, _reject);
}
const excuteRejected = () => {
const x = onRejected(this.reason);
this.promiseResolution(promise2, x, _resolve, _reject);
}

// 如果`onFulfilled`或者`onRejected`抛出一个异常`e`,`promise2`必须以`e`作为失败原因变成`rejected`状态
try{
if (this.status === 'fullfilled'){
excuteFullfilled();
} else if (this.status === 'rejected'){
excuteRejected();
} else {
this.fullFilledCallbacks.push(() => {
try{
excuteFullfilled();
}catch(e){
_reject(e);
}
});
this.rejectedCallbacks.push(() => {
try{
excuteRejected();
}catch(e){
_reject(e);
}
});
}
}catch(e){
_reject(e);
}

return promise2;
}

promiseResolution(){
...
}

接下来我们来实现promiseResolution函数,如果已经忘了可以去复习一下promiseResolution的规范。promiseResolution的主要任务是根据onFulFilled或者onRejected的返回值x来决定promise2的状态转变。

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
promiseResolution(promise, x, resolve, reject){
// 如果`resolvePromise`和`rejectPromise`都被调用了,或者以同样的参数被调用多次,则以第一次调用为准,忽略之后的调用
let called = false;

// 如果`resolvePromise`以参数`y`被调用的话,则执行`[[Resolve]](promise, y)`
const resolvePromise = (y) => {
if(!called){
this.promiseResolution(promise, y, resolve, reject);
called = true;
}
}

// 如果`rejectPromise`以参数`r`被调用的话,则以`r`作为失败原因使``变成`rejected`状态
const rejectPromise = (r) => {
f(!called){
reject(r);
called = true;
}
}

if(promise === x){
throw new Error('TypeError');
}

// 如果`x`是一个promise,则`promise`采用`x`的状态以及相应的结果值或者原因
if( x instanceof Promise){
x.then(resovle, reject)
}

if( x && (typeof x === 'object' || typeof x === 'function')){
let then = x.then;
if(typeof then === 'function'){
try{
then.call(x, resolvePromise, rejectPromise);
}catch(e){
if(!called){
reject(e);
}
}
}else{
// 如果`then`不是一个函数,则`promise`以`x`为结果值变为`fullfilled`状态
resolve(x);
}
} else {
// 如果`x`不是一个对象或者函数,则`promise`以`x`为结果值变为`fullfilled`状态
resolve(x);
}

}

异步问题

规范中有一条时,onFulfilled或者onRejected直到执行环境堆栈只包含平台代码时才可以执行。规范给出的注释是:平台代码是指引擎、执行环境和Promise实现代码,就是说js主栈中不能有其他代码,这就是要求我们异步执行onFulfilled或者onRejected;我们可以用宏任务(setTimeout 或者 setImmediate)或者微任务(MutationObserver or process.nextTick)机制来实现。

我们知道,ES6的promise的then方法回调异步执行的机制是微任务。在浏览器环境我们可以使用MutationObserver来模拟微任务机制,如果浏览器不支持MutationObserver,我们回退到setTimeout使用宏任务实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function isNative(fn){
return fn.toString.includes('native code');
}

function asynTask(){
if(typeof MutationObserver === 'function' && isNative(MutationObserver)){
return (fn) => {
var targetNode = document.createElement('div');
var config = { attributes: true };
// Create an observer instance linked to the callback function
var observer = new MutationObserver(fn);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
targetNode.id = 'anyway';
}
} else if(typeof setImmediate === 'function' && isNative(setImmediate)){
return (fn) => {setImmediate(fn)};
} else{
return (fn) => {setTimeout(fn, 0)};
}
}
class Promise{
registerAsyn: asynExcute()
}

我们使用异步机制来重写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
51
then(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
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
51
52
53
54
55
56
57
58
59
60
61
class Promise{
catch(fn) {
return this.then((null, fn);
}

finally(fn) {
const P = this.constructor;
return this.then(
value => P.resolve(fn()).then(() => value),
reason => P.resolve(fn()).then(() => { throw reason })
);
}
}

//resolve方法

Promise.resolve = function(value) {
if (value instanceof Promise) {
return value;
}

return new Promise(function(resolve) {
resolve(value);
});
};

Promise.reject = function(value) {
return new Promise(function(resolve, reject) {
reject(value);
});
};

Promise.race = function(promises) {
return new Promise(function(resolve, reject) {
for (var i = 0, len = promises.length; i < len; i++) {
promises[i].then(resolve, reject);
}
});
};

Promise.all = function(promises){
if (!promises || typeof promises.length === 'undefined')
throw new TypeError('Promise.all should accepts an array');
let values = [];
let i = 0;
let length = promises.length;
function processData(index,data){
values[index] = data;
i++;
if(i == length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
};
});
}

参考来源

https://juejin.im/post/5b2f02cd5188252b937548ab#heading-8

https://github.com/taylorhakes/promise-polyfill