参考:
V8 Promise源码全面解读 - 掘金
备份:V8 Promise源码全面解读_lengye7的博客-CSDN博客
上面这篇文章中的几个错误:
1、resolve不是对应于规范的FulfillPromise而是对应于Promise Resolve Functions。
2、reject不是对应于规范的RejectPromise而是对应于Promise Reject Functions。
上面这篇文章之中提到的TriggerPromiseReactions ( reactions, argument )由标准规定,会给reject添加一个handler,实际上,标准并没有提到。另外ECMAScript并没有规定该抽象操作的具体实现,只是规定了它应该起到的作用。在HTML sepc文档中对此做出了比较明确的解释,那里也规定了具体的实现方式,但是该实现方式并不是添加一个handler,该方式要求将没有handler的rejected状态的promise放入到一个unhandle集合中(about-to-be-notified),然后在事件循环过程中进行计算,通知用户。
(具体计算过程:查询该promise是否已经是handled rejection,如果不是,直接通知用户,如果已经是handled rejection,则将该promise移出unhandle集合,将其放入handled rejection集合中。)
对于V8来说,其应该是直接初始化了一个micro task,然后该micro task会计算(上述括号中的过程,V8实现的micro task计算是针对特定关联的promise来计算。),实际上,这个时候对于rejected 状态的promise来说,其并没有一个handler,处于unhandled状态。
当该micro task获得执行的时候,如果该rejected promise仍然不具备一个handler,那么就会在console通知用户,即抛出异常——unhandled rejection。另外,这个时候也会产生UnhandleRejection事件,同时该promise被放入到另外一个集合中(outstanding rejected promises weak set)。
about-to-be-notified:处于unhandled rejection状态即将触发unhandle rejection事件。
outstanding rejected promises weak set:处于unhandled rejecntion状态已经触发unhandle rejection事件。
那么自然还有第三种状态,handled rejection状态和已经触发unhandle rejection事件的promise,这种promise往往是在抛出异常之后,又添加了onRejected函数的promise,它们的unhandled rejection获得处理,当状态转变为此的时候,又会触发rejection handled事件。
Creating a JavaScript promise from scratch, Part 7: Unhandled rejection tracking - Human Who Codes
备份:Creating a JavaScript promise from scratch, Part 7: Unhandled rejection tracking_lengye7的博客-CSDN博客
《Chrome V8源码》25.最难啃的骨头——Builtin! - 知乎
备份:https://blog.csdn.net/lengye7/article/details/123992377
V8源码(在线阅读网站):https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-reaction-job.tq;l=73?q=PromiseReactionJob&ss=chromium%2Fchromium%2Fsrc
EcmaScript-262 2021标准文档
前言
深入部分看参考内容即可,如果遇到很大困难就不建议看了,干活也用不上。
深入进不去,浅出得不来。
正文
Promise的构造函数
Promise的构造函数有三个分支:
1、Promise只能当做构造函数,不能作为函数调用,否则触TypeError异常。
2、Promise的excutor必须是函数,否则触发TypeError异常。
3、excutor是同步执行的,excotor中参数有两个,这个是JS解释器自动生成,分别是resolve与reject,它们是函数。
Then函数
注意:一个promise对象的then函数可以调用多次,给一个promise对象设置多个回调处理函数。
Then函数其实就只有3个主要分支:
1、pending状态的promise
将onFulfilled和onRejected回调与新创建的一个promise对象作为一个整体,一起绑定到promise对象上,然后返回这个新创建的promise对象。
2、fulfilled状态的promise
将onFullfilled回调函数与当前promise对象中保存的的结果值(这个结果值一般由resolve产生或者在链式调用中,由上一个promise对象的执行的回调函数的返回值得到。)以及新创建的promise对象作为参数创建一个micro task,然后将该micro task放入到micro task队列中,返回新创建的promise对象。
3、rejected状态的promise
先判断当前promise对象是否已经具备handler(即onRejected处理函数),然后做相应的处理:具备handler,则不做任何处理;如果不具备,则会做相应的处理,将promise从相应的状态转变为rejection handled。
做完上面的事情,会将onRejected函数与promise对象中保存的结果值(这个结果值一般由reject产生或者在链式调用中,由上一个promise对象中执行的回调函数触发异常得到。)以及新创建的promise对象作为参数生成一个micro task,然后将这个micro task放入到micro task队列中,返回新创建的promise对象。
最后,不论是pending、fulfilled还是rejected状态,它们都会在最后将handler设置为true,表明当前promise具备handler了,即onFulfilled或者onRejected处理函数,即使此时这俩函数都为undefined也会将handler设置为true。
即只要调用了then,就说明具备了handler。
而能够返回promise对象,这也是其能够支持链式调用的关键所在,这个返回的promise对象与当前then函数所提供的onFulfilled和onRejected相绑定,会作为micro task的一部分。
实际上,一个micro task其实包含4部分内容:onFulfiled或者onRected处理函数,then函数返回的新创建的promise对象,当前promise对象的执行的结果值,micro task的类型:fulfilled或者rejected。
另外一些注意点:
如果onFulfilled与onReject这两个参数不是函数,那么对应的处理函数onFulfiled或者onRjected将会是undefined。
Resolve函数
记:resolve函数直接对应于规范的Promise Resolve Functions,V8中对应于ResolvePromise。
V8中的ResolvePromise省略了规范的前6步。
这里留下一个坑:resolve实际上会产生非常有意思的行为,后续会说到。
坑已填,看后面的Resolve函数补充。
该函数主要作用就是将pending状态的Promise的对象状态转变为fulfilled(只能处理pending状态的promise),并且根据对应的promise的所有的onFulfilled处理函数与resolve中的参数生成micro task,然后把所有的micro task扔到micro task队列中。
注意:如果一个promise没有调用过then、catch、finally,则不具备handler,调用Resolve也就不会产生micro task。
Reject函数
记:reject函数直接对应于规范的Promise Reject Functions,V8中对应于RejectPromise。
V8中的RejectPromise省略了规范的前6步。
该函数主要作用就是将pending状态的Promise的对象状态转变为rejected(只能处理pending状态的promise),并且根据对应promise是否具备handler来进行处理。如果不具备handler(只要调用过一次then,就说明具备了handler),则执行TriggerPromiseReactions ( reactions, argument )(关于这个处理,看开篇说的。)。如果具备了handler,则会根据promise对象所有的onRejected处理函数与reject中的参数生成对应micro task,并将其放入到micro task队列中去。
注意:如果一个promise没有调用过then、catch、finally,则不具备handler,调用Reject也就不会产生micro task。
Catch函数
Catch函数可以认为是then函数的简化版,约等于then(undefined,onRejected)。
所以catch函数也会将对应的promise对象设置被具备handler,其最后也会返回一个新的promise对象。
所以,只要调用了catch就说明promise具备了handler。
能够返回一个新创建的promise对象,也说明其支持链式调用。
Finally函数
Finally函数虽然最后也是通过调用then函数实现,但是它在调用之前还做了一些特殊处理。
这个特殊处理的地方在于,其会把onFinally重新用promise包装一层。
finally实现的Js代码大概如下:
Promise.prototype.finally = function _finally(onFinally) {
var promise = this;
var constructor = promise.constructor;
if (isFunction(onFinally)) {
thenFinally=function(value){
result=onFinally();
valueThunk=function(){
return value;
}
return constructor.resolve(result).then(valueThunk);
};
catchFinally=function(reason){
result=onFinally();
thrower=function(){
throw reason;
}
return constructor.resolve(result).then(thrower);
}
//最后调用then函数。
return promise.then(
thenFinally,
catchFinally
);
}
//当onFinally不是一个函数的时候,走这一条分支。
return promise.then(onFinally, onFinally);
}
在底层,finally仍然调用了then,所以finally函数也会将对应的promise对象设置为具备handler,其最后也会返回一个新的promise对象。
所以,只要调用了finally就说明promise具备了handler。
能够返回一个新创建的promise对象,说明其支持链式调用。
关于finally函数中的then finally function与catch finally function在27.2.5.3.1和27.2.5.3.2中。
链式调用与micro task的执行
先来看一段链式调用的Js代码:
let p0 = new Promise((resolve, reject) => {
reject(123)
})
// p0 的状态为 rejected
let p1 = p0.then(_ => {console.log('p0 onFulfilled')})
// p0 的 onRejected 作为 handler 进入 microtask 队列
// 但是因为 then 没有传递第二个参数
// 所以 onRejected 是 undefined,那么 handler 也是 undefined
let p2 = p1.then(_ => {console.log('p1 onFulfilled')})
/*
为p1绑定
PromiseReaction{
onFulfilled:_ => {console.log('p1 onFulfilled')},
onRejected:undefined
}
*/
let p3 = p2.then(_ => {console.log('p2 onFulfilled')}, _ => {console.log('p2 onRejected')})
/*
为p2绑定
PromiseReaction{
onFulfilled:_ => {console.log('p2 onFulfilled')},
onRejected:_ => {console.log('p2 onRejected')
}
*/
let p4 = p3.then(_ => {console.log('p3 onFulfilled')}, _ => {console.log('p3 onRejected')})
/*
为p3绑定
PromiseReaction{
onFulfilled:_ => {console.log('p3 onFulfilled')},
onRejected:_ => {console.log('p3 onRejected')
}
*/
//p2 onRejected
//p3 onFulfilled
这就是典型的链式调用,所谓链式调用就是根据上一个promise的micro task的执行结果,产生下一个promise的onFulfilled或者onRejected处理函数的micro task,直到最后一个promise没有相应的onFulfilled或者onRejected处理函数为止。
在上述例子中,p4没有通过then或catch绑定onFulfiled或者onRejected处理函数,链式调用到P4终止。
也就是说,只有当第一个promise的micro task执行完成之后,后续的promise才会产生micro task,然后继续执行,以此类推。
#micro task的执行
promise的micro task的执行主要有两大块核心逻辑:
1、如果handler=undefined(前面有提到过,具备handler,但是handler可以是undefined。),那么就直接将当前micro task对应的promise的结果值以及下一个promise作为参数调用FuflfillPromiseReactionJob(当前promise为fulfilled时)或者RejectPromiseReactionJob(当前promise为rejected时)。
2、如果handler(onFulfilled或者onRejected)是一个函数,则会执行handler函数。如果该handler执行过程中没有出现异常,则利用其返回值、下一个promise作为参数值调用FuflfillPromiseReactionJob。如果执行过程中出现了异常,那么就会把异常和下一个promise作为参数调用RejectPromiseReactionJob。
下一个promise是在调用then、catch、finally的时候绑定到上一个promise上的,所以上一个promise的micro task执行的时候,能够得到下一个promise。
看起来micro task的执行逻辑很简单,短短几句话就结束了,实际上并非如此。
真正复杂的部分都在FuflfillPromiseReactionJob与RejectPromiseReactionJob中,这两个调用在下一个部分,链式调用的micro task的触发里做详细的讲解。
#链式调用的micro task的产生
一般来说,链式调用的第一个micro task都是通过 在excutor中执行resolve或者reject将promise的状态转变为settled状态,然后再通过then函数产生micro task。
那么链式调用的后续的micro task是如何产生的呢?
以上面那个例子进行说明:
1、P0那一步执行完成时候,此时P0的状态为rejected状态,保存的结果值为123。
2、通过执行let p1 = p0.then(_ => {console.log('p0 onFulfilled')}),于是通过then函数产生了第一个micro task。
3、接下来继续执行同步代码,p1,p2,p3分别通过then函数绑定了onFulfilled和onRejected函数。
4、p0的rejected状态的micro task获得执行,由于p0的onRejected是undefined,所以就把p1和p0的结果值作为参数调用了RejectPromiseReactionJob(micro task的执行中第1点。),又由于p1之前通过then函数调用使自己具备了handler,于是一个onRejected micro task产生了。
5、p1对应的rejected状态的micro task获得执行,因为p1的onRejected是undefined,所以就把p2和p1的结果值作为参数调用RejectPromiseReactionJob,又由于p2之前通过then函数调用使自己具备了handler,于是一个onRejected micro task产生了。
6、p2对应的rejected状态的micro task获得执行,因为p2的onRejected是一个函数,所以这一次执行了onRjected这个回调函数(micro task的执行中的第2点),输出p2 onRejected。onRejected这个回调函数顺利获得执行,然后会将onRejected这个回调函数的返回值(这里的返回值为undefined)以及p3作为参数调用FuflfillPromiseReactionJob,由于p3之前通过then函数调用使自己具备了handler,于是一个onFulfilled micro task产生了。
7、p3对应的fulfilled状态的micro task获得执行,因为p3的onFulfilled是一个函数,所以这一次执行了onFulfilled这个回调函数,输出p3 onFulfilled。onFulfilled这个回调函数顺利得到执行,然后会将onFulfilled的返回值(这里是undefined)以及p4作为参数值调用FuflfillPromiseReactionJob,由于p4没有调用过then函数,所以其不具备handler,这一次没有再产生新的micro task,链式调用终止。
小结:
链式调用中,后续的micro task的产生,其实就2种情况:
handler:Promise对应状态的回调处理函数:onFulfilled或者onRejected。
1、handler为undefined,直接透传上一个Promise对象的结果值以及状态给下一个promise对象,然后根据下一个promise对象是否具备handler产生micro task。
2、handler是一个函数,如果handler执行中没有产生异常,则会将返回值作为参数,然后调用下一个promise的resolve函数。如果handler执行中产生了异常,则会将异常值作为参数,然后调用下一个promise的reject函数。然后在下一个promise对象的resolve和reject函数中,根据下一个promise对象是否具备handler产生micro task。
Resolve函数的补充
Resolve函数根据参数的不同,其执行流程会有所不同(3条分支):
1、参数不是一个具备then的对象,其会把对应的Promise状态转变为fulfilled,并根据Promise是否具备handler来产生micro task。这一点与前面的描述基本一致。
2、参数是一个带then方法的对象,这一点在前面没有提及。
这一条分支会把该resolve函数对应的promise对象的resolve和reject函数作为then方法的参数,创建一个then方法的micro task。
3、参数是resolve函数对应的promise对象,直接抛出TypeError异常。这一条分支,我实在想不到什么条件下触发。Js层面中,Resolve函数被调用要不就是在excutor中,要不就是在micro task中执行handler的时候,使用hanler的返回值作为下一个Promise对象的Resolve的参数。
1)、Resolve在excutor中被调用
这一种情况下,根本无法在excutor中访问到new Promise返回的promise对象,所以无法触发。
2)、链式调用中下一个Promise对象的Resolve在micro task中执行handler的时候,使用handler的返回值作为参数被调用
这一种情况是链式调用,那么如何才能在handler中获取到下一个Promise呢?Js层面似乎无法做到。
因此,对于Resolve函数的使用,应该保证参数不是一个具有then方法的对象。
异常在链式调用中的传递
在# mciro task的执行与# 链式调用的micro task的触发中,我提到,JS对于handler=undefined的情况,JS会直接传递上一个promise的状态以及结果值给下一个promise。
以一个例子来说明:
let p0 = new Promise((resolve, reject) => {
throw Error("123")
})
// p0 的状态为 rejected
let p1 = p0.then(() => {console.log('p0 onFulfilled')})
// p0 的 onRejected 作为 handler 进入 microtask 队列
// 但是因为 then 没有传递第二个参数
// 所以 onRejected 是 undefined,那么 handler 也是 undefined
let p2 = p1.then(() => {console.log('p1 onFulfilled')})
/*
为p1绑定
PromiseReaction{
onFulfilled:() => {console.log('p1 onFulfilled')},
onRejected:undefined
}
*/
let p3 = p2.then( ()=> {console.log('p2 onFulfilled')}, (err) => {console.log('p2 onRejected');console.log(err);})
/*
为p2绑定
PromiseReaction{
onFulfilled:() => {console.log('p2 onFulfilled')},
onRejected:(err) => {console.log('p2 onRejected');console.log(err);
}
*/
p0通过抛出异常将自己的状态设置为rejected,结果值为异常Error("123")。
由于p0的onRejected为undefined,所以handler为undefined,因此p0的状态与结果值传递给p1。
由于p1的onRjected为undefined,所以handler为undefined,因此p1的状态与结果值传递给p2。
p2的onRejected是一个函数,所以这一次执行onRejected对应的函数,异常得到处理,然后onRejected返回值为undefined。接下来,调用p3的resolve将undefined这个返回值传递p3,并设置p3的状态为fulfilled。
在链式调用中,excutor或者某一个回调处理函数中发生异常,会直接导致相应的promise的状态变为rejected,并且该promise的结果值为该异常,然后根据该promise是否具备hanlder产生micro task。当micro task获得执行的时候,如果handler为undefined,则传递该异常给下一个promise并设置下一个promise为rejected状态。一直这样传递下去,直到异常被处理或者抛出异常到console中。
一句话,异常在链式调用中如果得不到处理,就会一直往下传递,直到被处理为止或者最终抛出这个未处理异常到console。
总结
1、尽量保证resolve函数的参数不要是一个具有then方法的对象。
2、链式调用中,尽量保证handler返回值不是一个具有then方法的对象。(handler是onFulfilled或者onRejected函数)
3、链式调用中,始终在结束的地方添加一个catch,用于处理异常(别指望finally会处理异常,处理异常从来不是finally的功能)。
4、链式调用中,finally只用于链式调用的末尾,给链式调用添加一个处理流程。
5、宁愿将多个处理函数重新使用一个函数包装一遍,也不要使用then添加多个处理函数(then添加多个处理函数会增加维护成本的)。