Posts 重新理解 Node.js 事件循环
Post
Cancel

重新理解 Node.js 事件循环

   以前我对于 Node.js 的事件循环只有一个模糊概念, 就是 Node.js 执行完毕同步任务后(我理解的同步任务是指,在当前调用栈中执行的代码,而异步任务是指在在当前调用栈中被放置在回调函数里面的代码), 如果同步任务有回调, 那么就丢到事件循环队列中去.同步任务执行完毕后再去执行队列中的回调. 但是最近总是在想一个问题, Node.js 的事件循环有一个问题, 既然是队列,那肯定是遵循先进先出的原则. 那么下面这段代码的输出就应该是 1, 2, 3. 可是最终的输出结果是 2, 3, 1. 今天摸鱼的时候突然想知道为什么,最终我在阮一峰老师的文章下对 Node.js 事件循环有了新的理解.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
main = () => {
  setTimeout(() => {
    console.log(1);
  }, 5 * 1000);

  setTimeout(() => {
    console.log(2);
  }, 1 * 1000);

  setTimeout(() => {
    console.log(3);
  }, 3 * 1000);
}

main();
// output: 2, 3, 1

   Node.js 的异步任务有两种, 一种是追加在本轮事件循环中的异步任务, 一种是追加到次轮事件循环中的异步任务. Node.js 规定,process.nextTickPromise 的回调函数,追加在本轮循环,即同步任务一旦执行完成,就开始执行它们. 而 setTimeout(), setInterval(), setImmediate() 的回调函数,追加在次轮循环. 本轮循环的回调总是优先于次轮的回调, 而 process.nextTick() 的任务队列是则优先于 Promise 的任务队列执行.并且只有前一个队列全部清空以后,才会执行下一个队列. 所以在本轮循环中, 执行顺序是这样的: 1. 同步任务, 2. process.nextTick(), 3. Promise 的回调

   接着次轮循环的执行顺序, 首先事件循环并不是永不停止的,事件循环虽然会无限次地执行, 但只要异步任务的回调函数队列清空了, 就会停止执行. 而当又有同步任务的回调被放进队列中, 就会继续触发事件循环.下图是次轮事件循环中的执行顺序.

  • 我们可以看到第一个执行的是 timer 阶段. Node.js 有 4 个 timer, 他们是 setTimeout(), setInterval(), setImmediate(), process.nextTick(). process.nextTick() 的回调在上文已经说明过了. 而在次轮循环中, timer 阶段执行的是 setTimeout(), setInterval() 的回调.

  • 然后是 I/O callbacks 阶段, 除了 Node.js setTimeout(), setInterval(), setImmediate() 的回调, 以及用于关闭请求的回调函数,比如socket.on(‘close’, …)的回调, 其他的回调都在这个阶段执行.

  • 然后是 idle, prepare 阶段, 该阶段只供 libuv 内部调用,这里可以忽略.

  • 然后是 Poll 阶段, 这个阶段是轮询时间,用于等待还未返回的 I/O 事件,比如服务器的回应、用户移动鼠标等等.这个阶段的时间会比较长.如果没有其他异步任务要处理(比如到期的定时器),会一直停留在这个阶段,等待 I/O 请求返回结果

  • check 阶段, 该阶段执行 setImmediate() 的回调函数.

  • 最后是 close callbacks 阶段, 该阶段执行关闭请求的回调函数,比如socket.on(‘close’, …)

事件循环的六个阶段的流程图

   我们可以看到在 Poll 阶段, 如果没有其他异步任务要处理(比如到期的定时器), 则会一直停留在这个阶段, 等待 I/O 请求返回结果. 所以这也就能解答为什么最上面的代码块的输出结果为什么是 2, 3, 1 了. 到此我的疑问算是解开了, 这也让我对 Node.js 事件循环的有了新的理解.

   最后, 有一个有趣的问题, 就是 setTimeout(callback, 0)setImmediate() 的执行顺序.根据上文讲的事件循环的顺序, 理所应当的会认为肯定是 setTimeout(callback, 0) 先执行, 但是实际测试发现并不是下图是两次执行相同代码的运行结果. 这个问题阮一峰老师的文章有详细的讲解, 有兴趣的可以去看看 Ref 中的文章.

setTimeout() 和 setImmediate() 的执行顺序测试

Ref

  1. Node 定时器详解
This post is licensed under CC BY 4.0 by the author.

我的2021年终总结

对于 Promise.all() 的误解