Node 环境下的 Event Loop

114 阅读3分钟

之前的文章中讲了浏览器环境的Event Loop,今天我们再来看看Node 环境下的 Event Loop,在Node环境下,浏览器的EventLoop机制并不适用,切记不能混为一谈。Node中的Event Loop是基于libuv实现的:libuv是Node的新跨平台抽象层,libuv使用异步,事件驱动的编程方式,核心是提供i/o的事件循环和异步回调。libuv的API包含有时间,非阻塞的网络,异步文件操作,子进程等等。

Event Loop的6阶段

编辑切换为居中

添加图片注释,不超过 140 字(可选)

Node的Event loop一共分为6个阶段,每个细节具体如下:

  • timers: 执行setTimeout和setInterval中到期的callback。
  • pending callback: 上一轮循环中少数的callback会放在这一阶段执行。
  • idle, prepare:仅在内部使用。
  • poll:最重要的阶段,执行pending callback,在适当的情况下回阻塞在这个阶段。
  • check:执行setImmediate的callback。
  • close callbacks: 执行close事件的callback,例如socket.on(‘close’[,fn])或者http.server.on('close, fn)。

注意:上面六个阶段都不包括 process.nextTick()

编辑

添加图片注释,不超过 140 字(可选)

如上图所,在Node.js中,一次宏任务可以认为是包含上述6个阶段、微任务microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务。

process.nextTick()

在前面就已经了解到,process.nextTick()属于微任务,但是这里需要重点提及下:

  • process.nextTick()虽然它是异步API的一部分,但未在图中显示。因为process.nextTick()从技术上讲,它不是事件循环的一部分;
  • 当每个阶段完成后,如果存在 nextTick,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行(可以理解为微任务中优先级最高的)

实例分析

咱们通过demo来看看

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
console.log('13')

我们对代码的执行分区来进行一下分解

编辑切换为居中

添加图片注释,不超过 140 字(可选)

  • 1、整体代码可以看做宏任务,同步代码直接进入主线程执行,输出1,7,13,接着执行同层微任务且nextTick优先执行输出6,8;
  • 2、二层中宏任务中只存在setTimeout,两个setTimeout代码块依次进入6阶段中的timer阶段以t1、t2进入队列;代码等价于:
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
  • 3、setTimeout中的同步代码立即执行输出2,4,9,11,nextTick和Pormise.then进入微任务执行输出3,10,5,12;
  • 4、二层中不存在6阶段中的其他阶段,循环完毕,最终输出结果为:1->7->13->6->8->2->4->9->11->3->10->5->12;

到这里整个Event Loop的知识点就梳理完了,我们来总结一下:

浏览器和Node环境下,microtask 任务队列的执行时机不同:Node 端,microtask 在事件循环的各个阶段之间执行;浏览器端,microtask 在事件循环的 macrotask 执行完之后执行。