Node Event Loop
Node 事件循环
浏览器中事件循环根据 HTML5 定义的规范来实现, node 中则是基于 libuv 实现, libuv 是一个跨平台的专注于异步 IO 的库

上图 EVENT_QUEUE 给人看起来只有一个队列,但 EventLoop 存在 6 个阶段,每个阶段都有对应的一个先进先出的回调队列
循环流程
node 中事件循环分成以下六个阶段

timers,执行setTimeout、setInterval的回调I/O callbacks, 执行延迟到下一个循环迭代的I/O回调,即上一轮循环中未被执行的一些I/O回调idle,prepare, 闲置阶段,仅系统内部使用poll, 轮询阶段,检索新的I/O事件,执行与I/O相关的回调,node将在适当的时候在此阻塞check, 检查阶段,setImmediate回调函数在此执行close callbacks, 关闭事件回调,如socket.on('close', () => {})
每个阶段对应一个队列,当事件循环进入某个阶段时,将在该阶段内执行回调,直到队列耗尽或者回调的最大数量已执行,那就进行下一个处理阶段
process.nextTick
除了上述 6 个阶段,还有 process.nextTick ,他不属于事件循环中的任何一个阶段,他属于该阶段与下阶段之间的过渡,即本阶段执行结束,进入下一个阶段前,所要执行的回调,类似插队
微任务和宏任务
在 node 中,同样有宏任务与微任务的概念,与浏览器中的事件循环相似
微任务对应有
process.nextTickPromise.then、queueMicrotask
宏任务对应有
timers:setTimeout、setIntervalpoll queue:I/O事件check queue:setImmediateclose queue:close事件
其执行顺序为以此先微任务,后宏任务
常见题目
js
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout0')
}, 0)
setTimeout(function () {
console.log('setTimeout2')
}, 300)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick1'));
async1();
process.nextTick(() => console.log('nextTick2'));
new Promise(function (resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function () {
console.log('promise3')
})
console.log('script end')分析过程
- 先找到同步任务,输出
script start - 遇到第一个
setTimeout,将里面的回调函数放到timer队列中 - 遇到第二个
setTimeout,300ms后将里面的回调函数放到timer队列中 - 遇到第一个
setImmediate,将里面的回调函数放到check队列中 - 遇到第一个
nextTick,将其里面的回调函数放到本轮同步任务执行完毕后执行 - 执行
async1函数,输出async1 start - 执行
async2函数,输出async2,async2后面的输出async1 end进入微任务,等待下一轮的事件循环 - 遇到第二个
nextTick,将其里面的回调函数放到本轮同步任务执行完毕后执行 - 遇到
new Promise,执行里面的立即执行函数,输出promise1、promise2 then里面的回调函数进入微任务队列- 遇到同步任务,输出
script end - 执行下一轮回调函数,先依次输出
nextTick的函数,分别是nextTick1、nextTick2 - 然后执行微任务队列,依次输出
async1 end、promise3 - 执行
timer队列,依次输出setTimeout0 - 接着执行
check队列,依次输出setImmediate 300ms后,timer队列存在任务,执行输出setTimeout2
执行结果
js
script start
async1 start
async2
promise1
promise2
script end
nextTick1
nextTick2
async1 end
promise3
setTimeout0
setImmediate
setTimeout2再如以下题目
理解 process.nextTick 的优先级,还有 Promise.then 以及 setTimeout 和 setImmediate
js
setImmediate(() => {
console.log('setImmediate')
})
setTimeout(() => {
console.log('setTimeout1111')
setTimeout(() => {
console.log('setTimeout4444')
})
process.nextTick(() => {
console.log('setTimeout-nextTick')
})
})
setTimeout(() => {
console.log('setTimeout3333')
})
process.nextTick(()=> {
console.log('nextTick-outer')
new Promise((resolve) => {
console.log('promise')
resolve()
}).then(res => {
console.log('promise-then')
})
process.nextTick(() => {
console.log('nextTick-inner')
})
})
process.nextTick(() => {
console.log('nextTick-2222')
})
setTimeout(() => {
console.log('setTimeout2222')
})输出结果
js
nextTick-outer
promise
nextTick-2222
nextTick-inner
promise-then
setTimeout1111
setTimeout-nextTick
setTimeout3333
setTimeout2222
setImmediate
setTimeout4444