关于Promise和setTimeout的执行顺序,感觉自己理解的eventloop都是错的

用户bPtcBD
  • 550

看代码:

 setTimeout(() => {
   console.log(1);

   Promise.resolve().then(() => {
     console.log(4);
   });
 });
    
 Promise.resolve().then(() => {
   console.log(2);

   setTimeout(() => {
     console.log(3);
   });
 });

请输出上面的执行结果:...
浏览器的输出结果:2 1 4 3,
node的输出结果为: 2 1 3 4

这样子就有点疑惑了。。。。

2和1的顺序能理解,但是 4和3的顺序,感觉和自己理解的知识发生冲突了,

我的理解是 代码从上往下执行,
将第一个定时器,放到事件回调队列中,
将第一个peomise.then放到微任务中,
然后执行微任务中的代码,先输出2,
然后将第二个定时器塞入事件回调队列,
此时主线程空闲,在事件回调队列中取出第一个定时器的回调执行,打印1,
然后将promise放到微任务中,
此时就出现分歧了,此时的主线程或者执行栈是从微任务中取出promise来执行打印4还是取出定时器的函数执行打印3

从结果来看,执行栈选择了打印3,

从饿了么团队的一篇文章中看到这样一句话,node会清空当前所处阶段的队列,即执行所有task,再去检查微任务

望解惑,不论是浏览器或者node端,按我的思路道理都不通啊?

回复
阅读 1.5k
3 个回答
✓ 已被采纳

浏览器端循环策略:先执行当前循环宏任务,然后执行这个宏任务下的微任务(h5)。
node循环策略:先执行完本次循环的timer队列,然后执行timer下的微任务(libuv)。
以你这个例子来说,宏任务有:当前代码块,setTimeout,微任务有: Promise。

所以,浏览器端的表现是先执行整个代码块这个宏任务,然后执行这个宏任务下的微任务,即本例当中的(Promise.resolve().then),有且仅有这一个。因此,最先输出2,本次循环结束。
第二次循环找到宏任务(第一个setTimeout),打印出1,然后执行这个宏任务下的所有微任务,打印出4,第二次循环结束。
第三次循环,找到第二个setTimeout, 打印出3,第三次循环结束,所以浏览器端的输出结果是2 1 4 3。

而node端的表现开始与浏览器相同,执行整个代码块,完成微任务,输出2。然后找到第二次循环下的两个timer,依次输出 1,3.当前层级的timer都执行完毕之后,执行timer微任务 4, 所以node端的输出结果为 2 1 3 4。

总结:
浏览器: 代码块(宏任务)=> 微任务promise(输出2)=> 第一个timer(输出1)=>微任务promise(输出4)=>第二个timer(输出3)
node: 代码块=>微任务promise(输出2)=>第一个timer(输出1)=>第二个timer(输出3)=>微任务promise(输出4)

setImmediate(() => {
console.log(1);

process.nextTick(() => {

 console.log(4);

});
});

process.nextTick(() => {
console.log(2);

setImmediate(() => {

 console.log(3);

});
});
这段代码是那遍文章里面提到的, 文章说是输出 2134
但是你会发现把它扔到node的最新版中输出是 2143
旧一点的版本是 2134 这个表明node的异步执行机制已经改了,不是文章所说那样了

此时主线程空闲,在事件回调队列中取出第一个定时器的回调执行,打印1,
然后将promise放到微任务中,
此时就出现分歧了,此时的主线程或者执行栈是从微任务中取出promise来执行打印4还是取出定时器的函数执行打印3;

我觉的这里,当打印1,并且将promise放到微任务后,这时任务栈是空的,一旦任务栈空了,就会立刻检查微任务,打印4,
接着任务栈又空了,继续检查微任务,也是空,然后再次取出定时器

也就是说,当调用多个setTimeout在时间到达并且要回到栈执行的时候,它们是执行一个,清空栈,再执行下一个

视频资料参考(就这个时间的位置):https://youtu.be/8aGhZQkoFbQ?...

宣传栏