什么是事件循环
尽管JavaScript是单线程的,但通过尽可能将操作放到系统内核执行,事件循环允许Node.js执行非阻塞I/O操作。
由于现代大多数内核都是多线程的,因此它们可以处理在后台执行的多个操作。 当其中一个操作完成时,内核会告诉Node.js,以便可以将相应的回调添加到 轮询队列 中以最终执行。 我们将在本主题后面进一步详细解释。
事件循环解释
当Node.js启动时,它初始化事件循环,处理提供的输入脚本(或放入 ,本文档未涉及),这可能会进行异步API调用,调度计时器或调用 process.nextTick() , 然后开始处理事件循环。
下图显示了事件循环操作顺序的简要概述。
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
注意:每个框都将被称为事件循环的“阶段”。
每个阶段都要执行一个FIFO的回调队列。 虽然每个阶段都有其特殊的方式,但通常,当事件循环进入给定阶段时,它将执行特定于该阶段的任何操作,然后在该阶段的队列中执行回调,直到队列耗尽或最大回调数量为止 。 当队列耗尽或达到回调限制时,事件循环将移至下一阶段,依此类推。
由于这些操作中的任何一个可以调度更多操作并且在轮询阶段中处理的新事件由内核排队,因此轮询事件可以在处理轮询事件时排队。 因此,长时间运行的回调可以允许轮询阶段运行的时间比计时器的阈值长得多。 有关详细信息,请参阅和部分。
注意:Windows和Unix / Linux实现之间存在轻微差异,但这对于此演示并不重要。 最重要的部分在这里。 实际上有七到八个步骤,但我们关心的是 - Node.js实际使用的那些 - 是上面那些。
阶段概述
timer : 此阶段执行 setTimeout() 和 setInterval() 调度的回调
pending callbacks : 执行延迟到下一个循环迭代的I/O回调
idle, prepare : 只用于内部
poll : 检索新的I/O事件; 执行与I/O相关的回调(几乎所有回调都是带有异常的 close callbacks , timers 和 setImmediate() 调度的回调); node将在适当的时候阻塞在这里
check : 这里调用 setImmediate() 回调函数
close callbacks : 一些 close callbacks, 例如. socket.on(‘close', …)
在事件循环的每次运行之间,Node.js检查它是否在等待任何异步I / O或定时器,如果没有,则关闭。
阶段细节
定时器(timer)
计时器在一个回调执行完之后指定阈值,而不是人们希望的确切时间去执行。 定时器回调将在指定的时间过去后尽早安排; 但是,操作系统调度或其他回调的运行可能会延迟它们。
注意:从技术上讲,控制何时执行定时器。
例如,假设您计划在100毫秒后执行 timeout ,然后您的脚本将异步读取一个耗时95毫秒的文件: