node 的初始化
初始化 node 环境。
执行输入代码。
执行 process.nextTick 回调。
执行 microtasks。
进入 event-loop
进入 timers 阶段
检查 timer 队列是否有到期的 timer 回调,如果有,将到期的 timer 回调按照 timerId 升序执行。
检查是否有 process.nextTick 任务,如果有,全部执行。
检查是否有 microtask,如果有,全部执行。
退出该阶段。
进入 IO callbacks 阶段。
检查是否有 pending 的 I/O 回调。如果有,执行回调。如果没有,退出该阶段。
检查是否有 process.nextTick 任务,如果有,全部执行。
检查是否有 microtask,如果有,全部执行。
退出该阶段。
进入 idle,prepare 阶段:
这两个阶段与我们编程关系不大,暂且按下不表。
进入 poll 阶段
首先检查是否存在尚未完成的回调,如果存在,那么分两种情况。
第一种情况:
如果有可用回调(可用回调包含到期的定时器还有一些 IO 事件等),执行所有可用回调。
检查是否有 process.nextTick 回调,如果有,全部执行。
检查是否有 microtaks,如果有,全部执行。
退出该阶段。
第二种情况:
如果没有可用回调。
检查是否有 immediate 回调,如果有,退出 poll 阶段。如果没有,阻塞在此阶段,等待新的事件通知。
如果不存在尚未完成的回调,退出 poll 阶段。
进入 check 阶段。
如果有 immediate 回调,则执行所有 immediate 回调。
检查是否有 process.nextTick 回调,如果有,全部执行。
检查是否有 microtaks,如果有,全部执行。
退出 check 阶段
进入 closing 阶段。
如果有 immediate 回调,则执行所有 immediate 回调。
检查是否有 process.nextTick 回调,如果有,全部执行。
检查是否有 microtaks,如果有,全部执行。
退出 closing 阶段
检查是否有活跃的 handles(定时器、IO 等事件句柄)。
如果有,继续下一轮循环。
如果没有,结束事件循环,退出程序
event-loop

一段脚本总是自上而下的执行,同步代码阻塞执行,异步代码进入异步模块以非阻塞的方式执行
对应的回调函数会在异步代码执行完成之后被派发到不同的异步队列当中
- 同步代码执行
- 先执行 nextTick 中的任务,再执行微任务队列中的任务
- 最后是事件循环中的队列

各个阶段
整体阶段:
在事件循环的每一个子阶段退出之前都会按顺序执行如下过程:
- 检查是否有 process.nextTick 回调,如果有,全部执行。
- 检查是否有 microtaks,如果有,全部执行。
- 退出当前阶段
poll(轮询)阶段:
process.nextTick
为什么存在
- 有时要让回调在同步任务执行之后,事件循环开始之前执行(在事件触发过程中
- 允许用户处理错误,清理任何不需要的资源,或者在事件循环继续之前重试请求(TODO
版本差异
node11.x 之前,其事件循环的规则就如上文所述:先取出完一整个宏任务队列中全部任务,然后执行一个微任务队列。
但在 11.x 之后,node 端的事件循环变得和浏览器类似:先执行一个宏任务,然后是一个微任务队列。但依然保留了宏任务队列和微任务队列的优先级
console.log('Script开始')
setTimeout(() => {
console.log('宏任务1(setTimeout)')
Promise.resolve().then(() => {
console.log('微任务promise2')
})
}, 0)
setImmediate(() => {
console.log('宏任务2')
})
setTimeout(() => {
console.log('宏任务3(setTimeout)')
}, 0)
console.log('Script结束')
Promise.resolve().then(() => {
console.log('微任务promise1')
})
process.nextTick(() => {
console.log('微任务nextTick')
})在 node11.x 之前运行:
Script开始
Script结束
微任务nextTick
微任务promise1
宏任务1(setTimeout)
宏任务3(setTimeout)
微任务promise2
宏任务2(setImmediate)在 node11.x 之后运行:
Script开始
Script结束
微任务nextTick
微任务promise1
宏任务1(setTimeout)
微任务promise2
宏任务3(setTimeout)
宏任务2(setImmediate)在 node11.x 之前,微任务队列要等当前优先级的所有宏任务先执行完,在两个 setTimeout 之后才打印微任务 promise2;在 node11.x 之后,微任务队列只用等当前这一个宏任务先执行完
参考
Node.js 事件循环,定时器和 process.nextTick() | Node.js
阿里一面:熟悉事件循环?那谈谈为什么会分为宏任务和微任务。 - 掘金