Node
一、认识
Node.js
事件循环 是处理非阻塞 I/O
操作的机制。
Node.js
的事件循环基于libuv 引擎
一共有六个阶段,分别为timers 阶段
、pending callbacks 阶段
、idle,prepare 阶段
、poll 阶段
、check 阶段
、close callbacks 阶段
。每一个阶段都有一个FIFO
队列来执行回调。当事件循环进入某个阶段时,它将执行该阶段的任何操作,然后执行该阶段队列中的回调,知道队列用尽或者最大回调已经执行。如果队列已经用尽或达到回调限制,事件循环开始进入下一个阶段。
二、过程
当 Node.js
启动后,它会初始化事件循环,处理已提供的输入脚本(或丢入 REPL,本文不涉及到),它可能会调用一些异步的 API
、调度定时器,或者调用 process.nextTick()
,然后开始处理事件循环。事件循环操作顺序如下所示:
提示: 每个框被称为事件循环机制的一个阶段。
每个阶段都有一个 FIFO
队列来执行回调。虽然每个阶段都是特殊的,但通常情况下,当事件循环进入给定的阶段时,它将执行特定于该阶段的任何操作,然后执行该阶段队列中 的回调,直到队列用尽或最大回调数已执行。当该队列已用尽或达到回调限制,事件循环将移动到下一阶段,等等。
由于这些操作中的任何一个都可能调度 更多的 操作和由内核排列在轮询阶段被处理的新事件, 且在处理轮询中的事件时,轮询事件可以排队。因此,长时间运行的回调可以允许轮询阶段运行长于计时器的阈值时间。
阶段执行顺序: 外部输入数据-->轮询阶段(poll
)-->检查阶段(check
)-->关闭事件回调阶段(close callback
)-->定时器检测阶段(timer
)-->I/O事件回调阶段(I/O callbacks
)-->闲置阶段(idle, prepare
)-->轮询阶段(按照该顺序反复运行)...
2.1 timers 定时器阶段
执行setTimeout
和setInterval
中到期的callback
,执行这两者回调需要设置一个毫秒数,理论上来说,应该是时间一到就立即执行callback
回调,但是由于system
的调度可能会延时,达不到预期时间
2.2 pending callbacks 待定回调阶段
此阶段执行某些系统操作(例如TCP
错误类型)的回调。 例如,如果TCP socket ECONNREFUSED
在尝试connect
时receives
,则某些* 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
被添加到队列中,然后立即执行
- a.如果有
-
-
二、当然设定了
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(微任务)
,与浏览器表现一致