Node.js 11Version 版本之后 底层的任务执行栈的问题,宏任务与微任务执行的顺序做出了改变

在以往的Node版本中,也就是11.0 之前, JS的执行栈的顺序是

执行同类型的所有宏任务 -> 在间隙时间执行微任务 ->event loop 完毕执行下一个event loop

而在最新版本的11.0之后, NodeJS为了向浏览器靠齐,对底部进行了修改,最新的执行栈顺序和浏览器的执行栈顺序已经是一样了

执行首个宏任务 -> 执行宏任务中的微任务 -> event loop执行完毕执行下一个eventloop

但是这里是有问题的,虽然NodeJS的执行栈顺序向浏览器靠齐了, 但这是一种退步, 在整个JS的运行所消耗的时间上,是比之前的版本慢了一些,所以建议在项目中,还是使用11.0之前的版本,在12.0 或者之后的版本,Node官方将收尾工作 完成后, 再建议使用11.0之后的版本

在这里插入图片描述
在这里插入图片描述
Node.js的Event Loop过程:

  1. 执行全局Script的同步代码
  2. 执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务
  3. 开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,注意,这里是所有每个阶段宏任务队列的所有任务,在浏览器的Event Loop中是只取宏队列的第一个任务出来执行,每一个阶段的macrotask任务执行完毕后,开始执行微任务,也就是步骤2
  4. Timers Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue …
    下面这个图这就是Node的Event Loop【简化版】
    在这里插入图片描述
    NodeJS中 主要关注的宏任务队列有4个(Timers阶段、IO操作、immediates、close关闭操作),在这之中有两个微任务队列: nexttick、other micro tasks。

浏览器和Node端有什么不同?

  1. 浏览器的Event Loop和Node.js中的EventLoop是不同的,实现机制也不一样,不要混文一谈。
  2. Node.js中可以理解为4个宏任务队列和2个微任务队列,但是执行宏任务时有6个阶段。
  3. Node.js中,先执行全局Script代码,执行完同步代码调用栈清空后,先从微任务队列Next Tick Queue中依次取出所有的任务放入调用栈中执行,再从微任务队列Other Microtask Queue依次取出所有的任务放入调用栈中执行。然后开始宏任务的6个阶段,每个阶段都将该宏任务队列中的所有任务都取出来执行, 在执行下一阶段宏任务,以此来构成事件循环。
  4. MacroTask(宏任务)包括:setTimeout、setInterval、setImmediate(Node)、 requestAnimation(浏览器)、IO、UIrendering
  5. Microtask包括:process.nextTick(Node)、 Promise.then、Object.observe、MutationObserver
    注意:new Promise() 是构造函数里面的同步代码,而非微任务!

问题:微任务中的nextTick和then谁运行的快?

Promise.resolve('123').then(res=>{  console.log(res)})
process.nextTick(() => console.log('nextTick'))

代码经过运行之后可以看出nextTick 是比普通的微任务快一些的,这是为什么呢? 可以看看我们上面的那个NodeJS中的任务队列图, nextTick的microtask queue是优先于 promise的microtask queue。

setTimeout 和 setImmediate的问题

首先来看一段代码

setTimeout(() => {
	console.log('setTimeout')
}, 0);

setImmediate(() => {
	console.log('setImmediate')
})

运行结果:
在这里插入图片描述
为什么结果不确定呢?

setTimeout/setInterval 的第二个参数取值范围是:[1, 2^31 - 1],如果超过这个范围则会初始化为 1,即 setTimeout(fn, 0) === setTimeout(fn, 1)。

我们知道 setTimeout 的回调函数在 timer 阶段执行,setImmediate 的回调函数在 check 阶段执行,event loop 的开始会先检查 timer 阶段,但是在开始之前到 timer 阶段会消耗一定时间;
所以就会出现两种情况:

  1. timer 前的准备时间超过 1ms,满足 loop->time >= 1,则执行 timer 阶段(setTimeout)的回调函数
  2. timer 前的准备时间小于 1ms,则先执行 check 阶段(setImmediate)的回调函数,下一次 event loop 执行 timer 阶段(setTimeout)的回调函数。
    如果时间大于10ms 那么一定先执行timer阶段的setTimeout

如果setTimeout和setImmediate是几乎同一个时间执行的话,看setTimeout的准备阶段大不大,如果大的话,首先执行setTimeout 如果不大, 在看setTimeout的 延迟时间,如果小于1 , 最后的结果不稳定,如果大于1最后结果,先执行setImmediate

问题来了如何一定先执行setImmediate呢?

我们可以利用IO文件操作,来直接跳过Node的timers阶段

const fs = require('fs')

fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('setTimeout')
    }, 0)

    setImmediate(() => {
        console.log('setImmediate')
    })
})

这样的话,是在操作文件的流程中 运行的setTimeout、setImmediate, 所以已经太过了timers阶段,那么是从第二个阶段IO开始的, 所以一定会先执行setImmediate阶段

测试代码(巩固提升)

console.time("start")

setTimeout(function () {
    console.log(2);
}, 10);
setImmediate(function () {
    console.log(1);
});

new Promise(function (resolve) {
    console.log(3);
    resolve();
    console.log(4);
}).then(function () {
    console.log(5);
    console.timeEnd("start")
});
console.log(6);
process.nextTick(function () {
    console.log(7);
});
console.log(8);

在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值