Event Loop 阶段描述图
timers
timer 阶段处理 setTimeout 于 setInterval 回调, 开始处理的时机与 poll 阶段有关联.
pending callbacks
该阶段执行某些系统操作的回调, 比如 TCP 套接字在连接时收到 ECONNREFUSED.
网上有一些将该阶段称为 I/O callbacks 的文章都是过时错误的, 具体可以移步 Node.JS 官方库下面的这个 issue: #1118.
idle, prepare
内部使用, 忽略.
poll
poll 是一个核心阶段, 等新 I/O 事件的触发, 以及执行 I/O 相关回调. Node.JS 中出现异步的绝大部分情况都是 I/O 操作, 它们的回调基本都在这个阶段被执行.
poll 阶段主要做两件事:
计算需要为新的的 I/O 事件等待多久
当进入 poll 阶段, 如果队列为空且不存在 setImmediate 与就绪的 timer,Node.JS 会在这里 block 一定的时间等待新的 I/O 事件到来, 然后立即执行其回调. 这种情况具体 block 等待多久是不具体的, 但如果在 block 一定时间后仍没有新到达的 I/O 事件, 可以肯定循环依旧会进入 check 阶段或者回到 timer 阶段.
处理该阶段队列中的事件
当进入 poll 阶段, 如果队列不为空且没有就绪的 timer,Node.JS 会在这里执行队列中的 callback 直到队列为空或者执行的 callback 数达到系统设定的某个值. 随后 Node.JS 检查是否存在预设的 setImmediate, 存在话就进入 check 阶段, 否则开始检查 timer 就绪情况选择回到 timer 阶段或者进入 check 阶段.
对于 poll 阶段, 通过阅读官方的文档有些细节也没弄清楚, 用伪代码表示出来:
- enter pool phase:
- if (has timer scheduled) {
- // 官方没有提到这种情况会做什么
- }
- else {
- if (isEmpty(queue)) {
- if (has(setImmediate)) {
- // 进入 check 阶段
- }
- else if (!isEmpty(timer)) {
- // 回到 timer 阶段
- }
- else {
- // 等待新的 I/O 事件
- // 新的 I/O 事件触发回调立即执行, 执行完成之后的逻辑不清楚
- }
- // 目前看来只有存在 setImmediate 时才会进入 check 阶段, 这肯定不合理
- }
- if (!isEmpty(queue)) {
- let result = execute(queue);
- if (result === 'queue is empty') {
- // 官方没讲后续逻辑
- // 猜测是回到队列为空的处理逻辑中
- }
- if (result === 'reached hard limit') {
- // 官方没有解释这里的后续逻辑
- // 也许与 queue is empty 一样对待
- }
- }
- }
疑惑重点是从 poll 阶段出来的时机以及去向不是非常明确, 但以我目前的水平和精力只能到此为止.
check
当 poll 阶段执行完成会进入到 check 阶段执行, 该阶段的执行内容是所有 setImmediate 回调.
close callbacks
socket 的异常关闭,'close'事件的回调会在该阶段执行.
process.nextTick
process.nextTick 经常被用来做异步调用, 但它并不属于事件循环的内容, process.nextTick 中的回调被放在 nextTickQueue 中等待 "当前操作" 完成后被立即处理, 与事件循环中的阶段没有联系, 当前操作的原文定义是:"An operation is defined as a transition from the underlying C/C++ handler, and handling the JavaScript that needs to be executed.", 指的是在一段 JavaScript 代码执行完切换到 C/C++ 层时会处理 nextTickQueue.
文章提到了一个特例是 Deduplication, 这是 Node.JS 内部一个优化特性, 当在 timer 和 check 阶段, 同时有多个需要执行的回调时, 切换只会发生一次, 所以 nextTick 回调执行在这种情况下看似有所延后.
代码示例:
- setImmediate(() => {
- console.log('1');
- process.nextTick(() => console.log('2'));
- });
- setImmediate(() => {
- console.log('3');
- process.nextTick(() => console.log('4'));
- });
存在两个 setImmediate, 进入 check 阶段后需要在执行所有 setImmediate 的回调代码后才会产生切换, 从而执行 nextTick 回调, 因此上面代码的运行结果是:"1 3 2 4", 除上述场景外, nextTick 都会先于 setImmediate 执行.
总结
因为 Node.JS 的 Event Loop 我看了有那么 2,3 回, 但经常忘, 所以这次记录下来, 做个备忘. 由于太多知识容易忘记, 又发现写文章的一个优点:"帮助记忆便于复习".
最后
为了帮助大家让学习变得轻松, 高效, 给大家免费分享一大批资料, 帮助大家在成为全栈工程师, 乃至架构师的路上披荆斩棘. 在这里给大家推荐一个前端全栈学习交流圈: 866109386. 欢迎大家进群交流讨论, 学习交流, 共同进步.
当真正开始学习的时候难免不知道从哪入手, 导致效率低下影响继续学习的信心.
但最重要的是不知道哪些技术需要重点掌握, 学习时频繁踩坑, 最终浪费大量时间, 所以有有效资源还是很有必要的.
最后祝福所有遇到瓶疾且不知道怎么办的前端程序员们, 祝福大家在往后的工作与面试中一切顺利.
来源: http://www.jianshu.com/p/dccf12358446