跳到主要内容

Node

2024年04月07日
柏拉文
越努力,越幸运

一、认识


Node.js 事件循环 是处理非阻塞 I/O 操作的机制。

Node.js 的事件循环基于libuv 引擎 一共有六个阶段,分别为timers 阶段pending callbacks 阶段idle,prepare 阶段poll 阶段check 阶段close callbacks 阶段。每一个阶段都有一个FIFO队列来执行回调。当事件循环进入某个阶段时,它将执行该阶段的任何操作,然后执行该阶段队列中的回调,知道队列用尽或者最大回调已经执行。如果队列已经用尽或达到回调限制,事件循环开始进入下一个阶段。

二、过程


Node.js 启动后,它会初始化事件循环,处理已提供的输入脚本(或丢入 REPL,本文不涉及到),它可能会调用一些异步的 API、调度定时器,或者调用 process.nextTick(),然后开始处理事件循环。事件循环操作顺序如下所示:

Preview

提示: 每个框被称为事件循环机制的一个阶段。

每个阶段都有一个 FIFO 队列来执行回调。虽然每个阶段都是特殊的,但通常情况下,当事件循环进入给定的阶段时,它将执行特定于该阶段的任何操作,然后执行该阶段队列中 的回调,直到队列用尽或最大回调数已执行。当该队列已用尽或达到回调限制,事件循环将移动到下一阶段,等等。

由于这些操作中的任何一个都可能调度 更多的 操作和由内核排列在轮询阶段被处理的新事件, 且在处理轮询中的事件时,轮询事件可以排队。因此,长时间运行的回调可以允许轮询阶段运行长于计时器的阈值时间。

阶段执行顺序: 外部输入数据-->轮询阶段(poll)-->检查阶段(check)-->关闭事件回调阶段(close callback)-->定时器检测阶段(timer)-->I/O事件回调阶段(I/O callbacks)-->闲置阶段(idle, prepare)-->轮询阶段(按照该顺序反复运行)...

2.1 timers 定时器阶段

执行setTimeoutsetInterval中到期的callback,执行这两者回调需要设置一个毫秒数,理论上来说,应该是时间一到就立即执行callback回调,但是由于system的调度可能会延时,达不到预期时间

2.2 pending callbacks 待定回调阶段

此阶段执行某些系统操作(例如TCP错误类型)的回调。 例如,如果TCP socket ECONNREFUSED在尝试connectreceives,则某些* nix系统希望等待报告错误。 这将在pending callbacks阶段执行。

2.3 idle,prepare 阶段

仅系统内部使用。

2.4 poll 轮询阶段

  • poll工作如下:

    • 一、执行I/O回调
    • 二、poll阶段工作二、处理轮询队列中的事件
  • 逻辑如下:

    • 一、事件循环进入poll阶段并且在timers中没有可以执行定时器时,将发生以下两种情况之一

      • 1. 如果poll队列不为空,则事件循环将遍历其同步执行它们的callback队列,直到队列为空,或者达到system-dependent(系统相关限制)

      • 2. 如果poll队列为空,则会发生以下两种情况之一:

        • a.如果有setImmediate()回调需要执行,则会立即停止执行poll阶段并进入执行check阶段以执行回调
        • b.如果没有setImmediate()回到需要执行,poll阶段将等待callback被添加到队列中,然后立即执行
    • 二、当然设定了 timer 的话且 poll 队列为空,则会判断是否有 timer 超时,如果有的话会回到 timer 阶段执行回调

2.5 check 检测阶段

此阶段允许人员在poll阶段完成后立即执行回调。如果poll阶段闲置并且script已排队setImmediate(),则事件循环到达check阶段执行而不是继续等待。

setImmediate()实际上是一个特殊的计时器,它在事件循环的一个单独阶段运行。它使用libuv API来调度在poll阶段完成后执行的回调。

2.6 close callbacks 关闭的回调函数阶段

一些关闭的回调函数,如:socket.on('close', ...)

如果套接字或处理函数突然关闭(例如 socket.destroy()),则close事件将在这个阶段发出。否则它将通过process.nextTick()发出。

2.7 阶段末尾

在每次运行的事件循环之间,Node.js 检查它是否在等待任何异步 I/O 或计时器,如果没有的话,则完全关闭。

三、任务


3.1 Macro-Task (宏任务)

  • I/O

  • UI 交互事件

  • setTimeout

  • setInterval

  • setImmediate

  • MessageChannel

3.2 Micro-Task (微任务)

  • Promise.then

  • Object.observe

  • process.nextTick

  • MutationObserver

四、版本


4.1 Node 11 之前

同一个阶段全部MacroTask(宏任务)执行完毕完毕,才会执行MicroTask(微任务)

4.2 Node 11 之后

同一个阶段只要执行了MacroTask(宏任务),就会立即执行MicroTask(微任务),与浏览器表现一致

参考资料


Node.js 事件循环机制

「进击的前端工程师」一文带你搞懂JavaScript事件循环

又被node的eventloop坑了,这次是node的锅

一次弄懂Event Loop

「前端进阶」从多线程到Event Loop全面梳理

深入解析你不知道的 EventLoop 和浏览器渲染、帧动画、空闲回调(动图演示)