es6 Promise事件执行机制
最近在学习es6的promise,其中涉及到了promsie的事件执行机制,因此总结了关于promise的执行机制,若有错误,欢迎纠错和讨论。
在阮一峰老师的书中《es6 标准入门》对promise的基础知识做出了详细的介绍,在此就不一一介绍了,直接开始整体,将promsie中关于事件执行机制的问题与大家分享。
1.promsie对象的创建以及执行顺序
创建方式:new promise(function(resolve,reject){ //...}
这种方式在阮老师的书中指出该函数一旦创建,其内部的匿名函数会自动执行,在这里我们可以认为这种方式相当于同步任务会直接执行。以下代码亦可证明。
1 new promise(function(resolve,reject){ 2 console.log('1'); 3 }); 4 console.log('2');
结果:
结论:从结果的顺序来看,的确new promise会立即执行,执行完后再继续执行后面的同步任务。
2.promsie对象中的then()方法
promise中的then()会根据promsie的状态执行相应的回调函数,方法中有两个参数,分别是回调成功和回调失败时执行的函数。该函数是个异步任务。在此补充下关于js内部的机制。众所周知,js是单线程的,也就意味着无法同时做好几件事情,只能一件一件的做。代码执行时分为同步任务和异步任务,同步任务会在当前主线程中完成,执行中将变量和对象放入堆中,方法调用运行压入执行栈,需要变量时从堆中获取。而异步任务又可以分为microtask(微任务)和macrotask(宏任务),可以简单的理解为任务队列,通常将键盘事件和鼠标事件以及settimeout()等归为宏任务,而我们的promsie的then()归为微任务,执行中遇到他们会将他们放入相应的任务队列中。
在主线程的任务完成后会立即查看microtask是否有任务需要执行,若有则会执行直到队列为空队列,若本身为空则会查看macrotask任务队列,执行该队列中的第一个命令,当执行完第一条命令后会再次查看microtask是否有代码需要执行,同理,有则执行至空队列否则再次查看macrotask。
注意:这里的过程相当于macrotask任务队列的任务执行一次,则microtask任务队列的任务会全部执行。microtask全部任务执行完才会再去执行macrotask的下一条命令。
1 new promise(function(resolve,reject){ 2 resolve('1'); 3 }).then(function(value){ 4 console.log(value); 5 }).catch(function(error){ 6 console.log('出错了,',error); 7 }); 8 console.log('2');
结果:
结论:从上述结果来看,代码会立即执行new promise中的代码,此时prosmie状态改为resolved,而后调用then()方法,此时将该函数放入mircotask队列中,向下继续执行同步任务,待同步任务执行完后再回来执行mircotask中的函数。
3.promsie和settimeout的执行机制分析
此处直接引入一个例子作为介绍
1 console.log('1111111'); 2 settimeout(() => { 3 console.log('4----'); 4 new promise((resolve) => { 5 console.log('5-----'); 6 resolve(); 7 }).then(() => { 8 console.log('6----') 9 }) 10 }) 11 12 new promise((resolve) => { 13 console.log('22222'); 14 resolve(); 15 }).then(() => { 16 console.log('33333-----') 17 }) 18 19 settimeout(() => { 20 console.log('77777'); 21 new promise((resolve) => { 22 console.log('888'); 23 resolve(); 24 }).then(() => { 25 console.log('9999999') 26 }) 27 })
代码分析:
分析之前我们可以先在自己的小本本上写2列事件任务分类,分别是microtask微任务和macrotask宏任务,等下我们在分析代码时候可以模拟引擎的执行顺序将其写入小本子上,便于我们分析。
回归正题,1.首先第一行执行输出打印任务,这个是同步任务没什么说的,直接输出;
2.下一行遇到了settimeout()事件,先不管内部是什么函数,先将这个函数放入到我们的macrotask任务队列中第一行的位置等待执行;
3.又遇到了new promise,之前我们讲到,可以把它看成是同步任务所以直接执行其内部的代码,首先打印输出,之后遇到resovle(),此时promsie的状态改为resolved,并且值为‘22222’,之后调用then(),该方法是个异步任务,所以同样不管内部代码是什么,将其放入我们的microtask任务队列的第一行中等待执行;
4.之后又遇到了settimeout(),同样不管其内部代码直接加入macrotask任务中;
此时我们的同步任务执行完毕,我们整理下我们刚刚写下的2个任务队列中都有什么函数,在microtask中,有一个then()方法的函数,在macrotask任务队列中有2个按照执行顺序加入的settimeout()。
主线程任务执行后会直接去执行microtask的任务,所以下一步开始执行microtask任务队列中的函数,输出打印‘33333-----’,此时microtask任务队列中的任务队列执行完毕并且后面没有其他任务了,此时队列为空,这个时候会去执行macrotask的第一个函数,首先输出打印,遇到promise对象直接执行,并且状态改为resolved,执行then(),将其函数加入到microtask中,此时该settimeout执行完毕,这个时候会去再去看microtask是否为空,这个时候发现我们刚刚加入了一个指令,所以直接执行,执行完后microtask又没有任务了,这个时候再去执行macrotask的下一条任务,同样执行完再去执行我们刚刚再次向microtask加入的新任务,至此执行完毕。
大家可以看看是否和大家推到的一样。
4.关于resolve()参数的问题
在这里参数是非promise对象就不探讨了,调用resolve()和reject()就决定了promise对象的状态了。这里讨论下参数是promise对象的问题。首先,给出一段代码。
var p1=promise.resolve(); var p2=promise.reject('出错了'); var p3=new promise(function(resolve,reject){ resolve(p1); }).then(()=>console.log('p3状态为resolved'),()=>console.log('p3状态为reject')); var p4=new promise(function(resolve,reject){ resolve(p2); }).then(()=>console.log('p3状态为resolved'),()=>console.log('p3状态为reject'));
阮老师在书中指出传入参数为promsie对象时p1的状态会决定p2的状态。从上面的代码中可以看到,p1和p2直接决定了p3和p4的状态,不会管resolve(),也验证了阮老师的说法。另外,当resolve()改为reject()方法时,我们发现不管参数的状态是什么最后promise对象的状态都会变为reject。
另外,参数为promise对象时,引擎会先去获取参数的状态,获得状态在返回决定当前promise对象的状态,而获取promsie对象状态的过程我们可以认为是异步,也就是将此过程直接加入到microtask任务队列中。当参数是thenable对象时,会调用其中的then方法,该过程也可以看成是异步的。
1 var p1=promise.resolve(); 2 var p2=new promise(function(resolve,reject){ 3 resolve(p1); 4 }); 5 // p2.then(()=>console.log(2)); 6 console.log(p2); 7 settimeout(()=>console.log(p2),3000);
我们发现此时p2的状态并没有直接变为resolved,而是pending,在延迟3s后再判断p2的状态,此时已经变成了resolved,这样证实了我们的想法。参数为thenable对象时也表现出同样的结果。下面代码。
1 var p1={ 2 then(resolve,reject){ 3 resolve(); 4 } 5 } 6 var p2=new promise(function(resolve,reject){ 7 resolve(p1); 8 }); 9 console.log(p2); 10 settimeout(()=>console.log(p2),3000);
5.关于resovle(promise.resolve())的异步执行机制
此处问题也是困扰我好久的一个问题。这里首先需要验证下promise.resolve()是否是同步的。
1 var p1=promise.resolve(); 2 console.log(p1); 3 settimeout(()=>console.log(p1),3000);
从结果来看,p1确实是同步执行的。并且该方法会返回一个新的promise对象。回到正题,前面讲到resolve()参数是promsie对象时候去查询参数状态是个异步的,但这个时候我们发现在查询之前参数的状态其实已经确定了,引擎只是去获取状态,而此时我们的resolve(promise.resolve()),内部并非一个已经确定的promise对象,而是一个待执行的命令,因此这里会分两步走,第一步,异步执行执行promise.resolve()命令,此时将新产生的promsie对象的状态确定下来,name这个时候该函数就变成了前面我们讲到的那个样式,这个时候再执行异步操作获取参数的状态。
下面分析下之前困扰我好久的那个例子:
1 new promise((resolve, reject) => { 2 console.log("async1 start"); 3 console.log("async2"); 4 resolve(promise.resolve()); 5 }).then(() => { 6 console.log("async1 end"); 7 }); 8 9 new promise(function(resolve) { 10 console.log("promise1"); 11 resolve(); 12 }).then(function() { 13 console.log("promise2"); 14 }).then(function() { 15 console.log("promise3"); 16 }).then(function() { 17 console.log("promise4");
除ie外均测试,其结果一致。这里按照我们刚才的思维分析,首先输出async1和async2,此时将promise.resolve()压入microtask,继续向后执行,遇到新的promise,输出promise1,改变状态此时将then()的函数压入microtask,这个时候主线程执行完毕,开始执行microtask,promise.resolve()执行完毕,得到确定状态的新的promise对象,此时resolve()为获取该promsie状态将该过程加入到刚才的microtask任务队列中,这个时候任务队列有2个,一个是输出promise2的命令,其后面紧跟获取状态的命令,所以执行输出promise2的命令,执行完毕后又将下一个then()方法中的promise3命令添加到获取状态的后面,这个时候再去执行获取状态的命令,得到了状态执行resolve(),触发then(),将输出async end的命令添加到promise3输出的后面,下一步执行输出promise3,触发then(),将promise4输出命令加入到输出async end命令后面,这个时候执行async end输出和promise4输出,执行完毕。
至此,promise中的异步机制总结完了,欢迎大家留言指正和批评,和大家一起进步。
上一篇: 小米忙了10年 微信曾经的对手黯然退场