浅谈 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之后,但在较长延迟的定时器之前完成。
相关推荐
belhomme24 分钟前
(面试题)Netty 线程模型
java·面试·netty
vim怎么退出1 小时前
谷歌性能优化知识点总结
前端
专业抄代码选手1 小时前
在react中,TSX是如何转变成JS的
前端·javascript
葡萄城技术团队1 小时前
【实践篇】从零到一:手把手教你搭建一套企业级 SpreadJS 协同设计器
前端
忆江南2 小时前
# iOS Block 深度解析
前端
米丘2 小时前
vue-router v5.x 路由模式关于 createWebHistory、 createWebHashHistory的实现
前端
本末倒置1832 小时前
Bun 内置模块全解析:告别第三方依赖,提升开发效率
前端·javascript·node.js
踩着两条虫2 小时前
AI 驱动的 Vue3 应用开发平台 深入探究(二):核心概念之DSL模式与数据模型
前端·vue.js·ai编程
牛奶2 小时前
200 OK不是"成功"?HTTP状态码潜规则
前端·http·浏览器
Hilaku3 小时前
OpenClaw 很爆火,但没人敢聊它的权限安全🤷‍♂️
前端·javascript·程序员