浅谈 nodejs 的事件循环机制

前言

js 的事件循环机制可能大家都比较熟悉了,分为宏任务和微任务 。

但是 Node.js 的的事件循环机制不知道大家了解多少,这个在面试当中也是比较常问,如果有涉及到 Node.js的 的基本上都会问到事件循环机制,Node.js的事件循环机制和 js 的有所不同,但是大致的理念是相通的,接下来我们就来看一下吧!

核心

Node.js 的事件循环是其异步非阻塞 I/O 模型的核心。它允许 Node.js 在执行非阻塞操作时保持高效,使得单线程的 js 能够处理大量并发操作。

事件循环的基本阶段如下:

  • Timers( 定时器 :执行 setTimeout()setInterval() 设定的回调。
  • I/O callbacks( I/O回调 :处理几乎所有的 I/O 回调,除了 close 回调、setTimers 和 setImmediate() 的回调。
  • Idle, Prepare( 闲置、准备 :系统内部使用,处理一些准备工作。
  • Poll( 轮询 :检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有的 I/O 回调都在这个阶段处理,除了 close 回调,timers 和 setImmediate() 的回调);节点会在这个阶段检查是否有新的定时器到期,如果有,则回到 timers 阶段执行;如果没有新的 I/O 回调,并且当前也没有定时器到期,则会执行 setImmediate() 的回调。
  • Check( 检查 :执行 setImmediate() 设定的回调。
  • Close Callbacks( 关闭回调 :执行关闭事件的回调,如 socket.on('close')

在 Node.js 中,宏任务和微任务的概念与浏览器环境类似,但有一些特殊性。

宏任务:

  1. setTimeout()
  2. setInterval()
  3. setImmediate()
  4. I/O操作(如fs模块的文件操作)
  5. HTTP请求回调

微任务:

  1. Promise的then()、catch()和finally()回调
  2. process.nextTick()
  3. queueMicrotask()

执行顺序:

  1. 同步代码

  2. 微任务

    • process.nextTick()队列
    • 其他微任务队列(Promise等)
  3. 宏任务

在每个宏任务执行完毕后,Node.js都会清空微任务队列,然后再进入下一个事件循环阶段。

练习

为了更好的理解,看一段代码

js 复制代码
const fs = require('fs')

console.log('开始执行')

Promise.resolve().then(() => {
  console.log('Promise callback')
})

// 定时器
setTimeout(() => {
  console.log('定时器回调1')
}, 0)

setTimeout(() => {
  console.log('定时器回调2')
}, 10)

setTimeout(() => {
  console.log('定时器回调3')
}, 100)

// I/O操作
fs.readFile('./template.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('I/O错误:', err)
  } else {
    console.log('I/O回调:', data)
  }
})

// setImmediate
setImmediate(() => {
  console.log('setImmediate回调')
})

// process.nextTick
process.nextTick(() => {
  console.log('process.nextTick回调')
})

console.log('脚本执行结束')

运行结果:

js 复制代码
开始执行
脚本执行结束
process.nextTick回调
Promise callback
定时器回调1
setImmediate回调
I/O回调: 我是IO回调
定时器回调2
定时器回调3

解释:

  1. 宏任务执行:

    • "定时器回调1"(setTimeout 0ms)首先执行,这是符合预期的,因为它实际上被设置为1ms延迟。
    • "setImmediate回调"在"定时器回调1"之后、"I/O回调"之前执行。这说明在这次运行中,事件循环先进入了检查阶段(执行setImmediate),然后才到达了轮询阶段(执行I/O回调)。
    • "I/O回调"在setImmediate之后执行,这是完全可能的,因为I/O操作的完成时间是不确定的。
    • "定时器回调2"(setTimeout 10ms)在I/O回调之后执行,这说明10ms的延迟确实比I/O操作和setImmediate的执行时间长。
    • "定时器回调3"(setTimeout 100ms)最后执行,这是符合预期的,因为它有最长的延迟时间。

关键点:

  1. setImmediate和 I/O 回调的顺序: 在这次运行中,setImmediate回调在 I/O 回调之前执行。这展示了Node.js 事件循环的一个重要特性:检查阶段(执行 setImmediate )可能会在某些 I/O 操作完成之前到来。
  2. 定时器的行为: 0ms、10ms 和 100ms 的定时器确实按照预期的顺序执行,但它们的实际执行时间可能会受到其他操作(如I/O和setImmediate)的影响。
  3. I/O操作的不确定性: I/O回调的执行时机证明了异步I/O操作的完成时间是不可预测的,它可能在setImmediate之后,但在较长延迟的定时器之前完成。
相关推荐
k09333 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang135825 分钟前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning25 分钟前
React.lazy() 懒加载
前端·react.js·前端框架
时差95329 分钟前
【面试题】Hive 查询:如何查找用户连续三天登录的记录
大数据·数据库·hive·sql·面试·database
web行路人35 分钟前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
超雄代码狂1 小时前
ajax关于axios库的运用小案例
前端·javascript·ajax
长弓三石1 小时前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙
小马哥编程1 小时前
【前端基础】CSS基础
前端·css
嚣张农民1 小时前
推荐3个实用的760°全景框架
前端·vue.js·程序员
周亚鑫2 小时前
vue3 pdf base64转成文件流打开
前端·javascript·pdf