又被node的eventloop坑了,此次是node的锅

近日在论坛上看到一篇文章讲node和谷歌浏览器的eventloop的区别,由于看到写的还不错,我表示了确定。但没过多久一位坛友却说node11结果不同,我说怎么可能不同。接着坛友贴了个代码,我试着运行了一下,啪啪打脸!node

一探究竟

先上被啪啪打脸的代码:git

setTimeout(() => {
  console.log('timer1');
  Promise.resolve().then(function() {
    console.log('promise1');
  });
}, 0);
setTimeout(() => {
  console.log('timer2');
  Promise.resolve().then(function() {
    console.log('promise2');
  });
}, 0);
复制代码

了解node的eventloop的同窗应该会这样想:github

  1. 理想状况下这个就是一开始将两个setTimeout放进timers的阶段。
  2. 等到时间到达后运行timer1,把promise1的Promise放入timers的下一阶段微任务队列中,同理继续运行timers的阶段,执行timer2,把promise2的Promise放入timers的下一阶段微任务队列中。
  3. 直到timers队列所有执行完,才开始运行微任务队列,也就是promise1和promise2.

那么若是机器运行良好就是如下结果:promise

timer1
timer2
promise1
promise2
复制代码

node10运行结果确实是这样,是没问题的。但node11运行后竟然是:浏览器

timer1
promise1
timer2
promise2
复制代码

挺吃惊的,但吃惊事后仍是仔细去翻node的修改日志,在node 11.0 的修改日志里面发现了这个:异步

  • Timers
    • Interval timers will be rescheduled even if previous interval threw an error. #20002
    • nextTick queue will be run after each immediate and timer. #22842

而后分别看了20002和22842的PR,发如今 #22842 在lib/timers.js里面有如下增长:oop

timer.png

immediate.png

这两个是什么意思呢?ui

提示一下runNextTicks()就是process._tickCallback()。用过的可能知道这个就是除了处理一些异步钩子,而后就是执行微任务队列的。因而我增长了两行process._tickCallback()在setTimeout方法尾部,再使用node10运行,效果果真和node11一致,代码以下:spa

setTimeout(() => {
    console.log('timer1');
    Promise.resolve().then(function() {
        console.log('promise1');
    });
    process._tickCallback(); // 这行是增长的!
}, 0);
setTimeout(() => {
    console.log('timer2');
    Promise.resolve().then(function() {
        console.log('promise2');
    });
    process._tickCallback(); // 这行是增长的!
}, 0);

复制代码

那么为何要这么作呢?

固然是为了和浏览器更加趋同。3d

了解浏览器的eventloop可能就知道,浏览器的宏任务队列执行了一个,就会执行微任务。

简单的说,能够把浏览器的宏任务和node10的timers比较,就是node10只有所有执行了timers阶段队列的所有任务才执行微任务队列,而浏览器只要执行了一个宏任务就会执行微任务队列。

如今node11在timer阶段的setTimeout,setInterval...和在check阶段的immediate都在node11里面都修改成一旦执行一个阶段里的一个任务就马上执行微任务队列。

最后

因此在生产环境建议仍是不要特地的去利用node和浏览器不一样的一些特性。即便是node和浏览器相同的特性,但规范没肯定的一些特性,也建议当心使用。不然一次小小的node升级可能就会形成一次线上事故,而不仅是啪啪打脸这么简单了。