Promise 源码分析
这个周编码过程中使用了NodeJs去处理图片,因为图片处理的接口全都是Promise,就导致了我的程序中充满了Promise的嵌套返回,then的结果中有返回了另一个Promise,另一个Promise中一系列中的then中有的又会返回新的Promise,这一度让我变得混乱。
理清楚后,不禁对Promise为什么能够如此神奇产生了好奇,于是去阅读了它的源码,才发现有些习以为常的功能背后的设计的奇妙。
Promise 使用
ES6提供Promise构造函数,我们创造一个Promise实例,Promise构造函数接收一个函数作为参数,这个传入的函数有两个参数,分别是两个函数 resolve
和reject
作用是,resolve
将Promise的状态由未成功变为成功,将异步操作的结果作为参数传递过去;相似的是reject
则将状态由未失败转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。
实例创建完成后,可以使用then
方法分别指定成功或失败的回调函数,比起f1(f2(f3))的层层嵌套的回调函数写法,链式调用的写法更为美观易读
我们在创建一个新的Promise的时候需要传入一个函数作为参数,这个传入的函数有两个参数,分别是两个函数,resolve以及reject,函数体内部执行异步操作,然后根据操作的结果是否为需要的结果调用不同的函数,如果获得了正确的返回,就调用resolve函数,并将你需要返回的结果作为参数,如果没有获取正确的返回,就调用reject函数,并将错误信息作为参数,这样外部就可以获取错误信息了。举个简单的例子:
1 | const promise = new Promise((resolve, reject) => { |
这个例子创建了一个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
11const 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 | //example1 |
当参数是一个非promise的时候,1秒后promise的状态立即变成resolve,并执行then里面的事件.
1 | //example2 |
当参数是另一个promise的时候,这时promise1的状态由promise2来决定,什么时候promise2变化了状态,promise1的状态也会相应的变化,并且状态保持一致.
1 | //example3 |
当回调函数里面直接return一个非promise,和上面的example1一样,当前的promise2状态变为resolve。相当于执行了(resolve(‘非promise’))
1 | //example4 |
当回调函数里面直接return一个promise3,和上面example2一样,当前promise2的状态依赖于primise3,相当于执行了(resolve(promise3))
1 | //example5 |
当回调函数里面代码报错了,并且没有被catch到的,当前promise状态变为reject.(异步的error代码catch不到,不会影响promise状 态变化)
catch的规则
- 执行reject的时候会调用catch
- 前面出现任何没有被处理的错误时会执行catch
源码分析
下面的注释说明了对Promise状态的可能值:
0 - 等待中
1 - 满足条件 (值为 _value)
2 - 拒绝条件 (值为 _value)
3 - 采用了另一个Promise的状态和值
一旦状态值不为0, 那么这个Promise将不可以被修改.
在正式声明Promise之前,为了减少对try catch在代码中显示,定义了几个工具函数,
工具函数
1 | function noop() {} //空回调函数,用于then |
声明
1 | module.exports = Promise; |
函数Promise接受一个函数作为其参数,必须通过new来创建。
初始化 _deferredState 和 _state为 0, _value和_deferreds为null,
如果传入函数是一个空函数,那么直接返回。
正常情况下,进入 doResolve 开始流程。
doResolve
1 | function doResolve(fn, promise) { |
这里同步的直接调用传入的函数,讲两个函数 (即外部编写的resolve和reject)作为参数传入 , 调用完成后检查下是否是没完成的情况下出错了,如果是直接reject.
针对传入的resolve函数和reject函数,等待结果后,如果尚未完成,则通过本文件中定义的resolve和reject来继续流程.
注意tryCallTwo函数的第二个和第三个参数都是一个函数,这两个函数的参数其实就是我们new一个新的Promise的时候传入的两个函数,而这段代码中的resolve和reject则是Promise内部定义的方法。
resolve & reject
1 | function resolve(self, newValue) { |
1 | //这个函数只有self的状态不为0的时候才会执行,这个时候执行handle就会执行传入的第二个参数,也就是_deferreds,_deferredState为1表明_deferreds是一个Handler,_deferredState为2说明_deferreds是Handler的数组,全都执行完了,再把_deferreds赋值为空 |
然就冒出来之前未曾触及到的_deferredState和_deferreds的使用,他们是什么用的?
在回答这些之前,我们先回想下在Promise的时候中,当创建并返回了promise之后,下面的操作就是then来获取结果了 (当然也包括catch), 那么对于这个then的实现我们先来看一下:
then
1 | //then的参数是两个函数,onFulfilled是我们resolve时调用的回调函数,onRejected是我们reject时调用的回调函数 |
其中的safeThen和then的用法基本一致,都是创建了一个异步的空回调res,然后使用onFulfilled, onRejected和res来创建 Handler。那么核心就在handle这个函数上了:
1 | function handle(self, deferred) { |
这个函数的参数就是deferred, 那么说明deferred就是Handler, 结合意思,指代的就是延迟的处理。明白点说,就是完成promise之后所需要做的事情。
那么具体的过程是怎么样的呢?
首先判断当前状态是不是依赖于另一个promise, 是的话则通过while等待
然后onHandle只是个提供给外部的进度回调,这里先无视
当状态为0的时候,这里就是设置未来的处理过程了,
1 | 如果未来状态没有设置过(0), 那么设置回调(deferred) 为单独回调 |
当状态非0的时候, 就进入了handleResolved,这应该就是完成后处理结束的地方了。 等等,上面只是从then出发进入handle的,那时候应该promise还没有完成,处理完成的调用一定是在别的地方。 通过搜索handle的调用可以看到还有在finale函数中, 这样就和上面连接上了,我们先回顾下什么时候会调用finale呢?
1 | 1 状态3 等待其他promise的结果时候 - 这里会进入等待 |
可以看到只有在promise结束或者依赖其他promise的时候,才会进入finale.
1 | function finale(self) { |
finale中会将之前放入的deffereds 一一取出 调用handle, 这时state均为非0,直接进入handleResolved, 代码如下:
1 | function handleResolved(self, deferred) { |
这里就比较简单的,通过异步的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 | const promise = new Promise((resolve, reject) => { |
这段代码中,setTimeout这段代码是会直接执行的,然后创建了一个宏任务,也就是1s之后执行resolve或者reject,然后下面的then其实和catch其实是创建了两个Handler,而这两个Handler会在调用resolve或者reject之后,被asap这个包丢进微任务里。
所以,我们一直说的Promise是微任务,其实指的是then和catch里传入的函数会在状态改变时被放入微任务队列。
参考文章:
https://juejin.im/post/5caf147af265da035d0c698a