Nodejs 的事件循环机制是如何处理 timers、poll 和 check 队列的?

浏览器中 JS的事件循环相信大家都很熟悉。nodejs 是和操作系统打交道, 所以学习起来会有一点难度。

一、node事件循环图

Nodejs 的事件循环机制是如何处理 timers、poll 和 check 队列的?

这里面会涉及到一些多线程, 本文只讨论跟 JS 线程有关的信息。

二、执行过程

代码的运行首先会经过正常的执行, 当**同步代码**执行完毕之后就会去看还有没有其余线程的信息, 比如 setTimeout 这里就进入上图中的 event loop 右边的循环执行。

每一次循环会经过 timers >> pending callback >> idle prepare >> poll >> check >> close callbacks 这六个阶段(一次循环)

这六个都是队列

这里主要讨论 timers、poll、check 队列

timers (计时器队列)

  • timers 队列主要负责处理计时器的回调函数

poll (轮询队列)

  • poll 存放除了 timers、check 绝大部分回调都会放在该队列,比如:读写文件内容的回调函数,监听用户的请求的回调函数。
import { readFile } from 'fs';

readFile('/etc/passwd', (err, data) => { // 该回调函数进入的是 poll 队列
  if (err) throw err;
  console.log(data);
});
  • poll队列的运作方式

1、如果 **poll**中有回调,依次执行回调,直到清空队列。

2、如果 **poll**中没有回调

  • 等待其它队列中出现回调,结束该阶段,进入下一阶段
  • 如果其它队列也没有回调,持续等待,知道出现回调为止

check (检查阶段)

使用 setImmediate 的回调会直接进入这个队列。

setImmediate 函数可以当做是: setTimeout(callback,0)

setImmediate(() => {
    console.log(123)
})

setTimeout(() => {
    console.log(345)
}, 0)
// 输出的顺序 有个能345 在前也有可能 123
// 因为 setTimeout 不存在取到 0 只能 >0, 所以当进入 timers 队列时不一定存在 回调函数。 

setImmediate所以它一定是异步的, 但是异步回调函数进入的是 check 队列。

现在大致了介绍 timer、poll、check 队列,下面用一段代码解释执行的顺序。

const fs = require('fs')

fs.readFile('./index.js', 'utf-8', function test () {
    console.log('读取文件的回调函数')

    setTimeout(() => {
        console.log(1)
    }, 0)

    setImmediate(() => {
        console.log(2)
    })
})

这段代码的执行结果为

读取文件的回调函数
2
1

使用事件循环的角度来分析为什么是这个输出结果:

代码执行到读取文件, 当文件读取完时会触发 test 回调函数, test回调函数加入 poll 队列, **poll**队列执行 test回调函数, 输出 读取文件的回调函数, 然后执行到了 setTimeout 那么会通知**计时器**线程监听(当时间到达时, 才会将setTimeout的回调函数加入 timers 队列),继续执行到 setImmediate直接将回调函数加入**check**队列, 然后test执行完毕,此时**poll**队列执行完毕, 然后监听其它队列是否有需要执行的信息, 发现**check**队列需要执行,然后就到了**check**队列执行输出2,**check**也执行完毕然, 然后此时 计时器timers 加入了回调函数(加入的时间点并不能确定), 所以当 check 执行完毕时又进入了行的事件循环, 此时 timers 有需要执行的信息, 然后输出 1, 当 timers 执行完毕,系统发现没有什么需要执行的就结束了程序执行。

解释图中的 NextTick、Promise

NextTick、Promise 也是队列,但是它们不同,上面的事件队列可以说是 **宏任务**, 它们两则是 微任务 它们并不存在事件循环中, 它们两有一个优先级:先执行 NextTick 然后在执行 Promise

NextTick、Promise 怎么配合事件循环的

用一句话说就是:事件循环中,每次打算执行一个 回调函数 必须先清空 NextTick、Promise 列表

还是使用一段代码解释:

process.nextTick(() => {
    console.log(1)
    process.nextTick(() => {
        console.log(2)
    })
})

console.log(3)

Promise.resolve().then(() => {
    console.log(4)
    process.nextTick(() => {
        console.log(5)
    })
})

setTimeout(() => {
    console.log(6)
})

输出的结果为 3 1 2 4 5 6

  1. 先输出同步代码 3
  2. 进入 NextTick 输出 1, 然后又往 NextTick 加入回调函数, 由于优先级问题马上执行 NextTick 输出2
  3. NextTick 队列清空后, 在执行 Promise 队列输出 4,然后又往 NextTick 队列加入回调函数, 当 Promise 执行完毕时,此时 NextTick 有信息马上执行 输出 5
  4. 此时 NextTick、Promise 队列已经清空了然后进入事件循环 timers 输出 6

三、总结

如果想要最快的执行异步回调函数,那么可以使用将回调函数加入 NextTikc 队列的api, 其次是 **Promise**, nodejs 的事件循环了解之后对我们的日常工作起到一定的好处。

延展阅读:

CAP理论与Raft协议如何在分布式系统中确保一致性和可用性?

如何使用lafclient迁移函数?

如何在 Umi 中实现高效的国际化配置?

为什么PHP在近年来逐渐失去了领先地位,而GoLang却迅速崛起?

如何利用Postman高级功能提升接口开发效率?

咨询方案 获取更多方案详情                        
(2)
研发专家-文松研发专家-文松
上一篇 2024年7月4日 上午11:43
下一篇 2024年7月5日 上午11:14

相关推荐