Node中的事件循环

Node 中的异步 API

  • 定时器:setTimeout、setInterval
  • I/O 操作:文件读写、数据库操作、网络请求…
  • Node 独有的 API:process.nextTick、setImmediate

事件循环的流程

流程

Node事件循环分为6个阶段,这6个阶段会按顺序反复运行,每个阶段都有一个队列。 运行到某个阶段时,都会从该阶段对应的队列中取出回调函数执行。 当队列为空时就会进入下一个阶段。

  • timer 阶段:处理 setTimeout、setInterval 的回调,由 poll 阶段控制
  • pending callbacks 阶段:处理系统级别的回调。eg: TCP 连接失败的回调…
  • idle, prepare 阶段:仅 Node 内部使用,可以忽略
  • poll 阶段:处理 I/O 操作的回调,文件读写、数据库操作、网络请求…
  • check 阶段:执行 setImmediate 的回调
  • close callbacks 阶段:执行关闭请求的回调。eg: socket.on('close', ...)…

我们主要关注 timer poll check 阶段即可。 注意队列执行的都是回调,例如setTimeout(fn, 2000)表示2秒会把回调函数放入timer队列,一开始timer队列为空,会进入下一个阶段。

代码示例

console.log('脚本开始');
setTimeout(() => {
    console.log('定时器');
}, 10);
fs.readFile('demo.txt', (_, data) => {
    console.log(data);
});
setImmediate(() => {
    console.log('setImmediate');
});
console.log('脚本结束');

输出结果:'脚本开始' - '脚本结束' - 'setImmediate' - '定时器' - data

运行流程:

  • 主线程从上往下执行代码,遇到异步代码则开启新线程执行,然后主线程继续往下执行。同步代码执行结束后,开始事件循环
  • 执行到 timer 阶段:此时定时器还在执行中,所以 timer 队列为空;执行到 poll 阶段:此时 I/O 操作也还在执行中,poll 队列也为空。事件循环查看 check 队列和 timer 队列是否为空,发现 check 队列中有setImmediate 的回调,事件循环继续往下执行;执行到 check 阶段:取出 check 队列的回调执行,输出 'setImmediate';事件循环继续往下执行,并暂停在 poll 阶段
  • 10ms 后定时器执行完毕,回调加入 timer 队列,事件循环继续执行,输出 '定时器';事件循环执行到 poll 阶段并在此处暂停
  • 20ms 后 I/O 操作执行完毕,回调加入 poll 队列,事件循环继续执行,输出 data

微任务队列

常见的宏任务:setTimeout、setInterval、setImmediate、script(整体代码)、I/O 操作…
常见的微任务:process.nextTick()、new Promise().then catch finally…
微任务比宏任务的优先级高,所以会在执行宏任务之前先清空微任务队列
微任务中,nextTick 的优先级较高,会先执行

setTimeout(() => {
    console.log('timeout');
}, 0);
Promise.resolve().then(() => {
    console.log('promise');
});
process.nextTick(() => {
    console.log('nextTick');
});

输出结果:nextTick - promise - timeout