Promise 源码分析

这个周编码过程中使用了NodeJs去处理图片,因为图片处理的接口全都是Promise,就导致了我的程序中充满了Promise的嵌套返回,then的结果中有返回了另一个Promise,另一个Promise中一系列中的then中有的又会返回新的Promise,这一度让我变得混乱。

理清楚后,不禁对Promise为什么能够如此神奇产生了好奇,于是去阅读了它的源码,才发现有些习以为常的功能背后的设计的奇妙。

Promise 使用

ES6提供Promise构造函数,我们创造一个Promise实例,Promise构造函数接收一个函数作为参数,这个传入的函数有两个参数,分别是两个函数 resolvereject作用是,resolve将Promise的状态由未成功变为成功,将异步操作的结果作为参数传递过去;相似的是reject则将状态由未失败转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。
实例创建完成后,可以使用then方法分别指定成功或失败的回调函数,比起f1(f2(f3))的层层嵌套的回调函数写法,链式调用的写法更为美观易读

我们在创建一个新的Promise的时候需要传入一个函数作为参数,这个传入的函数有两个参数,分别是两个函数,resolve以及reject,函数体内部执行异步操作,然后根据操作的结果是否为需要的结果调用不同的函数,如果获得了正确的返回,就调用resolve函数,并将你需要返回的结果作为参数,如果没有获取正确的返回,就调用reject函数,并将错误信息作为参数,这样外部就可以获取错误信息了。举个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const num = Math.random();
num > .5 ? resolve(`success:${num}`) : reject(`fail:${num}`);
}, 1000);
});

promise.then((resolveVal) => {
console.log(val);
}).catch(rejectVal => {
console.log(rejectVal)
})

这个例子创建了一个Promise,在一秒钟之后生成一个随机数,如果随机数大于0.5就返回调用resolve,如果小于0.5,如果小于0.5就调用reject。

当执行了resolve时,Promise的状态会变为resolved,并会执行then中传入的函数,其实就相当于then中传入的函数会被当作resolve函数执行。同理,当执行了reject时,Promise的状态会变为Rejected,这个时候会利用catch中传入的函数作为reject函数去执行。

总的来说,then和 catch时为Promise加载了两个resolve和reject时具体需要执行的函数。

then的规则

  • then方法下一次的输入需要上一次的输出

  • 如果一个promise执行完后 返回的还是一个promise,会把这个promise 的执行结果,传递给下一次then中。也就是说如果你在PromiseA的then中返回了PromiseB,那么PromiseB的结果会作为PromiseA下一步then的入参。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const promiseA = new Promise((resolve, reject) => {
    ...
    });
    const promiseB = new Promise((resolve, reject) => {

    })
    promiseA.then((resolveA) => {
    return promiseB;
    }).then((resolveB) => {

    })

    这种情况下第二个then的参数就是promise的返回结果。

  • 如果then中返回的不是Promise对象而是一个普通值,则会将这个结果作为下次then的成功的结果

  • 如果当前then中失败了 会走下一个then的失败

  • 如果返回的是undefined 不管当前是成功还是失败 都会走下一次的成功

  • then中不写方法则值会穿透,传入下一个then

  • then函数中的return val 与Promise resolve(val)相同

举例说明:

1
2
3
4
5
6
//example1
promise1 = new Promise((resolve) => {
setTimeout(() => {
resolve('promise1');
}, 1000)
})

当参数是一个非promise的时候,1秒后promise的状态立即变成resolve,并执行then里面的事件.

1
2
3
4
5
6
7
8
9
//example2
promise1 = new Promise((resolve) => {
setTimeout(() => {
promise2 = new Promise((resolve, reject) => {
resolve('promise2');
})
resolve(promise2);
}, 1000)
})

当参数是另一个promise的时候,这时promise1的状态由promise2来决定,什么时候promise2变化了状态,promise1的状态也会相应的变化,并且状态保持一致.

1
2
3
4
5
6
7
//example3
promise1 = new Promise((resolve) => {
resolve('promise1');
})
promise2 = promise1.then((data) => {
return 'promise2';
})

当回调函数里面直接return一个非promise,和上面的example1一样,当前的promise2状态变为resolve。相当于执行了(resolve(‘非promise’))

1
2
3
4
5
6
7
8
9
10
//example4
promise1 = new Promise((resolve) => {
resolve('promise1');
})
promise2 = promise1.then((data) => {
promise3 = new Promise((resolve, reject) => {
resolve('promise3');
})
return promise3;
})

当回调函数里面直接return一个promise3,和上面example2一样,当前promise2的状态依赖于primise3,相当于执行了(resolve(promise3))

1
2
3
4
5
6
7
//example5
promise1 = new Promise((resolve) => {
resolve('promise1');
})
promise2 = promise1.then((data) => {
console.log( iamnotundefined );
})

当回调函数里面代码报错了,并且没有被catch到的,当前promise状态变为reject.(异步的error代码catch不到,不会影响promise状 态变化)

catch的规则

  • 执行reject的时候会调用catch
  • 前面出现任何没有被处理的错误时会执行catch

源码分析

Promise源码地址

下面的注释说明了对Promise状态的可能值:

0 - 等待中
1 - 满足条件 (值为 _value)
2 - 拒绝条件 (值为 _value)
3 - 采用了另一个Promise的状态和值

一旦状态值不为0, 那么这个Promise将不可以被修改.

在正式声明Promise之前,为了减少对try catch在代码中显示,定义了几个工具函数,

工具函数

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
function noop() {} //空回调函数,用于then

// States:
//
// 0 - pending
// 1 - fulfilled with _value
// 2 - rejected with _value
// 3 - adopted the state of another promise, _value
//
// once the state is no longer pending (0) it is immutable

// All `_` prefixed properties will be reduced to `_{random number}`
// at build time to obfuscate them and discourage their use.
// We don't use symbols or Object.defineProperty to fully hide them
// because the performance isn't good enough.


// to avoid using try/catch inside critical functions, we
// extract them to here.
var LAST_ERROR = null;
var IS_ERROR = {};
function getThen(obj) {
try {
return obj.then;
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}

function tryCallOne(fn, a) {
try {
return fn(a);
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
function tryCallTwo(fn, a, b) {
try {
fn(a, b);
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}

声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = Promise;
function Promise(fn) {
if (typeof this !== 'object') {
throw new TypeError('Promises must be constructed via new');
}
if (typeof fn !== 'function') {
throw new TypeError('Promise constructor\'s argument is not a function');
}
this._deferredState = 0;//这个状态用于表明未来的状态会是怎么样的,只有当前的Promise状态依赖另一个Promise时才有用,也就是resolve了一个Promise
this._state = 0;//当前Promise的状态
this._value = null;//当前Promise的resolve的值
this._deferreds = null;//当_deferredState变为成功,也就是大于2时,执行的回调函数数组
if (fn === noop) return;
doResolve(fn, this);
}

函数Promise接受一个函数作为其参数,必须通过new来创建。

初始化 _deferredState 和 _state为 0, _value和_deferreds为null,
如果传入函数是一个空函数,那么直接返回。
正常情况下,进入 doResolve 开始流程。

doResolve

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function doResolve(fn, promise) {
var done = false;
//注意,fn就是我们new Promise时传入的(resolve, reject)=> { if success resolve else reject},tryCallTwo会将第二个参数传给resolve,将第三个参数传给reject,这样当我们在声明的Promise中调用resolve时实际上调用的时trayCallTwo的第二个参数。
var res = tryCallTwo(fn, function (value) {
if (done) return;// 防止运行两次
done = true;
resolve(promise, value);
}, function (reason) {
if (done) return;
done = true;
reject(promise, reason);
});
if (!done && res === IS_ERROR) {
done = true;
reject(promise, LAST_ERROR);
}
}

这里同步的直接调用传入的函数,讲两个函数 (即外部编写的resolve和reject)作为参数传入 , 调用完成后检查下是否是没完成的情况下出错了,如果是直接reject.
针对传入的resolve函数和reject函数,等待结果后,如果尚未完成,则通过本文件中定义的resolve和reject来继续流程.

注意tryCallTwo函数的第二个和第三个参数都是一个函数,这两个函数的参数其实就是我们new一个新的Promise的时候传入的两个函数而这段代码中的resolve和reject则是Promise内部定义的方法

resolve & reject

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
function resolve(self, newValue) {
// 一个Promise的解决结果不能是自己 (因为根据一开始我们提到的then的原则中,如果你返回了一个新的Promise,那么当前Promise的状态就会依赖于新的Promise,如果自己依赖自己,那么就会一直循环依赖并处于pending状态)

// 这里的newValue其实就是我们定义的Promise实例中resolve的参数,依照前面的使用方法,它可以是字符串,数组等,也可以是另一个一个Promise
if (newValue === self) {
return reject(
self,
new TypeError('A promise cannot be resolved with itself.')
);
}
// 当新的值也就是resolve函数的参数存在并且类型是对象或者函数的时候
// typeof Promised实例 === 'object'
// 也就是说这个if成功的条件是resolve了一个对象,函数或者Promise实例
if (
newValue &&
(typeof newValue === 'object' || typeof newValue === 'function')
) {
var then = getThen(newValue); // let then = newValue.then
if (then === IS_ERROR) { //IS_ERROR就是上面所说的工具中声明的,就是一个空对象,只有当return newValue.then发生异常时才会为IS_ERROR
return reject(self, LAST_ERROR);
}
if (
then === self.then &&
newValue instanceof Promise // 如果resvole的是一个Promise,并且这个Promise的then与当前Promise的then相同时(这个then一般是相同的,都是定义在Promise的原型上的),直接就用当前Promise的结果为最终结果。
) {
self._state = 3; //状态3说明采用另一个Promise作为结果
self._value = newValue;
finale(self); // 那么采用这个Promise的结果
return;
} else if (typeof then === 'function') {//走到这里就说明,resolve了一个有then方法的对象(或者一个Promise,并且和当前Promise的then不同,这种情况比较少见)
doResolve(then.bind(newValue), self); // 递归调用doResolve,刚才我们也看了,doResolve的第一个参数是我们new Promise时传入的函数,第二个参数是当前的Promise实例的指针,也就是在递归中执行到这个then函数时,其内部的this指向的是这里的newValue,这里self没变
return;
}
}
//上面的if都没有return,才会走到这里,到了这里就说明,resolve了普通的值,比如数字,字符串,标记完成,进入结束流程
self._state = 1; //状态1说明进入成功状态
self._value = newValue;
finale(self);
}

function reject(self, newValue) {
//设置reject状态和理由
self._state = 2;
self._value = newValue;
if (Promise._onReject) {
Promise._onReject(self, newValue); //过程回调通知
}
finale(self); //结束
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//这个函数只有self的状态不为0的时候才会执行,这个时候执行handle就会执行传入的第二个参数,也就是_deferreds,_deferredState为1表明_deferreds是一个Handler,_deferredState为2说明_deferreds是Handler的数组,全都执行完了,再把_deferreds赋值为空
//也就是说finale是把所有的Handler执行一次
// 每个Hander都是通过then方法传入的,then方法有两个参数,分别是onFulfilled,onRejected,也就是resolve时应该执行的函数和reject时应该执行的函数
// 如果是_state为3的时候执行finale,其实就是先把当前Promise通过then创建的Handler挂到了它所依赖地Promise实例的_deferreds上去
function finale(self) {
if (self._deferredState === 1) {
handle(self, self._deferreds);
self._deferreds = null;
}
if (self._deferredState === 2) {
for (var i = 0; i < self._deferreds.length; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null;
}
}

然就冒出来之前未曾触及到的_deferredState和_deferreds的使用,他们是什么用的?
在回答这些之前,我们先回想下在Promise的时候中,当创建并返回了promise之后,下面的操作就是then来获取结果了 (当然也包括catch), 那么对于这个then的实现我们先来看一下:

then

1
2
3
4
5
6
7
8
9
//then的参数是两个函数,onFulfilled是我们resolve时调用的回调函数,onRejected是我们reject时调用的回调函数
Promise.prototype.then = function(onFulfilled, onRejected) {
if (this.constructor !== Promise) {
return safeThen(this, onFulfilled, onRejected);
}
var res = new Promise(noop);
handle(this, new Handler(onFulfilled, onRejected, res));
return res;
};

其中的safeThen和then的用法基本一致,都是创建了一个异步的空回调res,然后使用onFulfilled, onRejected和res来创建 Handler。那么核心就在handle这个函数上了:

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
function handle(self, deferred) {
//self._state === 3说明self指向的Promise实例的状态依赖另一个Promise实例,这个实例就是self._value
//也就是说通过这个循环,最终self最终会指向一个状态只依赖自己的Promise实例
//也就是说假设self是p1,它依赖p2的状态,当p1调用handle的时候,并且p2还没有resolve,其实是把p1通过then创建的Handler挂到了p2的_deferreds上去,当p2 resolve的时候依次执行。
while (self._state === 3) {
self = self._value;
}
if (Promise._onHandle) { // for injection - not in main loop
Promise._onHandle(self);
}
//如果这个状态只依赖自己的Promise实例还没有结果,就把传入的回调函数先保存起来
// _deferredState 为0表明还没有保存过回调函数,那么就把回调函数赋值给_deferreds,这个时候_deferreds只是一个回调函数
// _deferredState 为1表明保存过回调函数,那么就把回调函数和原有的保存在_deferreds的回调函数构造成一个数组重新赋值给_deferreds,这个时候_deferreds只是一个回调函数的数组
// _deferredState 为2表明_deferreds已经是一个回掉函数的数组了,就push就可以了
// 直到某一次调用handle,self的状态不为0了,才会执行handleResolved
if (self._state === 0) {
if (self._deferredState === 0) {
self._deferredState = 1;
self._deferreds = deferred;
return;
}
if (self._deferredState === 1) {
self._deferredState = 2;
self._deferreds = [self._deferreds, deferred];
return;
}
self._deferreds.push(deferred);
return;
}
handleResolved(self, deferred);
}

这个函数的参数就是deferred, 那么说明deferred就是Handler, 结合意思,指代的就是延迟的处理。明白点说,就是完成promise之后所需要做的事情。
那么具体的过程是怎么样的呢?
首先判断当前状态是不是依赖于另一个promise, 是的话则通过while等待
然后onHandle只是个提供给外部的进度回调,这里先无视
当状态为0的时候,这里就是设置未来的处理过程了,

1
2
3
4
如果未来状态没有设置过(0), 那么设置回调(deferred) 为单独回调
如果未来状态设置过了 (1), 那么设置回调 (deferred) 进入回调数组
如果其他状态 (2 +),那么直接进入回调数组。
对状态0情况下的处理这里就返回了。 因为在这个时候,是promise同步执行过来的then, 设置好未来处理的函数过程。

当状态非0的时候, 就进入了handleResolved,这应该就是完成后处理结束的地方了。 等等,上面只是从then出发进入handle的,那时候应该promise还没有完成,处理完成的调用一定是在别的地方。 通过搜索handle的调用可以看到还有在finale函数中, 这样就和上面连接上了,我们先回顾下什么时候会调用finale呢?

1
2
3
1 状态3 等待其他promise的结果时候 - 这里会进入等待
2 状态1 完成的时候
3 状态2 reject的时候

可以看到只有在promise结束或者依赖其他promise的时候,才会进入finale.

1
2
3
4
5
6
7
8
9
10
11
12
function finale(self) {
if (self._deferredState === 1) {
handle(self, self._deferreds);
self._deferreds = null;
}
if (self._deferredState === 2) {
for (var i = 0; i < self._deferreds.length; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null;
}
}

finale中会将之前放入的deffereds 一一取出 调用handle, 这时state均为非0,直接进入handleResolved, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function handleResolved(self, deferred) {
asap(function() {
// self._state 为 1,就执行then函数的第一个参数,就是成功的回调函数,否则执行reject的回调函数
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
if (self._state === 1) {
resolve(deferred.promise, self._value);
} else {
reject(deferred.promise, self._value);
}
return;
}
var ret = tryCallOne(cb, self._value);
if (ret === IS_ERROR) {
reject(deferred.promise, LAST_ERROR);
} else {
resolve(deferred.promise, ret);
}
});
}

这里就比较简单的,通过异步的asap调用,如果没有onFulfilled(onRejected失败情况),则直接调用resolve(reject), 如果有则先调用onFulfilled(onRejected失败情况),根据结果来调用resolve(reject)。

等等。。这里的resolve和reject不是在上面的流程中有出现了么?请注意这里resolve和 rejected的promise, 这个promise是在then的时候创建的空promise,也就是意味这什么都不会执行 (直接进入finale 无handle情况)。 所以真正影响这里流程的是 对于deferred.onFulfilled 或者 deferred.onRejected的回调执行,执行完回调 这个promise的执行过程就完成了。

综上, promise的执行过程是这样的

  • 创建Promise
  • 设置需要执行的函数,也就是new Promise是传入的函数
  • 设置完成的回调,也就是then传入的两个函数,第一个是resolve时执行的,一个是reject执行的,如果resolve了一个Promise2,那么当前then创建的Handler会挂载到Promise2上,等到Promise2 resolve了再一起执行。
  • 开始执行函数
  • 根据执行结果选择回调

另外提一句safeThen, safeThen的作用是当调用then的时候环境this已经不是Promise的情况下能够继续安全执行then。

Promise是微任务具体是什么意思

我们看完了源码再重新回头理解Promise为什么是微任务

还是以开始的代码为例子:

1
2
3
4
5
6
7
8
9
10
11
12
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const num = Math.random();
num > .5 ? resolve(`success:${num}`) : reject(`fail:${num}`);
}, 1000);
});

promise.then((resolveVal) => {
console.log(val);
}).catch(rejectVal => {
console.log(rejectVal)
})

这段代码中,setTimeout这段代码是会直接执行的,然后创建了一个宏任务,也就是1s之后执行resolve或者reject,然后下面的then其实和catch其实是创建了两个Handler,而这两个Handler会在调用resolve或者reject之后,被asap这个包丢进微任务里。

所以,我们一直说的Promise是微任务,其实指的是then和catch里传入的函数会在状态改变时被放入微任务队列。

参考文章:

https://juejin.im/post/5caf147af265da035d0c698a

https://juejin.im/entry/599968f6518825244630f809

https://www.jianshu.com/p/b63ec30eefdd