Skip to content

事件循环

什么是事件循环

事件循环(Event Loop)是 Node.js 处理异步操作的核心机制。它负责处理所有的异步操作、事件和回调函数,使得 Node.js 能够在单线程环境下高效地处理并发请求。

Node.js 的架构

1. V8 引擎

  • 执行 JavaScript 代码
  • 内存管理
  • 垃圾回收

2. Libuv

  • 跨平台的异步 I/O 库
  • 事件循环的实现
  • 线程池管理
  • 文件系统操作
  • 网络操作

3. 核心模块

  • fs(文件系统)
  • http(HTTP 服务器)
  • net(网络)
  • dns(域名解析)

4. 第三方模块

  • 来自 npm 的包

事件循环的阶段

Node.js 的事件循环分为以下几个阶段:

1. timers 阶段

  • 执行 setTimeout() 和 setInterval() 的回调
  • 检查定时器是否到期

2. pending callbacks 阶段

  • 执行 I/O 操作的回调
  • 处理上一轮循环中未执行的回调

3. idle, prepare 阶段

  • 内部使用
  • 仅在 libuv 内部使用

4. poll 阶段

  • 执行 I/O 操作
  • 检查新的 I/O 事件
  • 执行 I/O 回调
  • 计算阻塞时间

5. check 阶段

  • 执行 setImmediate() 的回调

6. close callbacks 阶段

  • 执行关闭事件的回调
  • 如 socket.on('close', ...)

事件循环的执行流程

  1. 执行同步代码
  2. 执行微任务(process.nextTick() 和 Promise 回调)
  3. 进入事件循环
  4. 执行 timers 阶段的回调
  5. 执行 pending callbacks 阶段的回调
  6. 执行 idle, prepare 阶段
  7. 执行 poll 阶段:
    • 如果有 I/O 事件,执行回调
    • 如果没有 I/O 事件,等待新的事件
  8. 执行 check 阶段的回调
  9. 执行 close callbacks 阶段的回调
  10. 重复以上过程

微任务和宏任务

微任务

  • process.nextTick()
  • Promise 的 then()、catch()、finally() 回调
  • Async/Await(基于 Promise)

宏任务

  • setTimeout()
  • setInterval()
  • setImmediate()
  • I/O 操作
  • 事件回调

process.nextTick()

  • 优先级高于 Promise
  • 在当前操作完成后立即执行
  • 即使在事件循环的不同阶段之间

setImmediate() vs setTimeout()

  • setImmediate() 在 check 阶段执行
  • setTimeout() 在 timers 阶段执行
  • 如果在主模块中同时调用,执行顺序取决于系统性能
  • 如果在 I/O 回调中调用,setImmediate() 总是先于 setTimeout() 执行

事件循环的最佳实践

1. 避免阻塞操作

  • 不要在事件循环中执行 CPU 密集型操作
  • 使用 worker threads 处理 CPU 密集型任务
  • 合理使用异步 I/O

2. 合理使用定时器

  • 根据需求选择合适的定时器
  • 避免过多的定时器
  • 注意定时器的精度

3. 处理错误

  • 正确处理异步操作的错误
  • 使用 try-catch 处理同步错误
  • 使用错误事件监听

4. 内存管理

  • 避免内存泄漏
  • 及时释放不再使用的资源
  • 合理使用缓存

常见问题

1. 回调地狱

  • 嵌套的回调函数
  • 代码难以维护
  • 解决方案:使用 Promise 或 Async/Await

2. 事件循环阻塞

  • CPU 密集型操作
  • 同步 I/O 操作
  • 解决方案:使用 worker threads 或子进程

3. 内存泄漏

  • 未释放的事件监听器
  • 闭包引用
  • 解决方案:及时移除事件监听器,避免循环引用

实践练习

  1. 编写一个示例,演示事件循环的不同阶段
  2. 比较 setImmediate() 和 setTimeout() 的执行顺序
  3. 演示微任务和宏任务的执行顺序
  4. 解决一个回调地狱问题,使用 Promise 或 Async/Await

基于 VitePress 的本地知识库