3.3 Node的异步I/O

20 阅读2分钟

好,我们进入第三章的核心重头戏:3.3 Node的异步I/O

这一节是全章(甚至全书)最精彩、最硬核的部分。朴灵作者用清晰的层层递进方式,完整拆解了Node.js异步I/O的全过程:从JS调用fs.readFile,到底层libuv如何处理,再到回调最终执行。

这一节分为四个子小节:

  • 3.3.1 事件循环
  • 3.3.2 观察者
  • 3.3.3 请求对象
  • 3.3.4 执行回调

作者用一张经典的“Node异步I/O全流程图”串起整个过程(几乎所有读者笔记都会重绘这张图),流程大致是:
JS层 → C++绑定层 → libuv请求对象 → 线程池/系统异步 → 完成回调 → 事件循环触发JS回调。

下面是一些经典的Node异步I/O流程图、事件循环阶段图、请求对象示意图(来自读者笔记、官方文档和原书风格重绘,这些图是理解这一节的“神器”):

3.3 Node的异步I/O(整体概述)

Node的异步I/O不是V8原生支持的,而是通过C++内置模块(如fs、net)桥接到libuv实现的。整个过程可以概括为四个阶段:

  1. 初始化事件循环(Node启动时libuv创建loop)。
  2. 用户调用异步API(如fs.readFile),JS层创建回调。
  3. C++层封装请求对象,投递给libuv(可能进线程池或直接系统调用)。
  4. I/O完成后,libuv把回调推入事件队列。
  5. 事件循环轮询到相应阶段,执行JS回调。

现在我们从第一个子小节开始。

3.3.1 事件循环

这是Node的“心脏”——Event Loop

  • Node的事件循环由libuv实现,和浏览器的Event Loop类似,但更强大(支持文件I/O、子进程等)。

  • 事件循环分为多个阶段(phases),每个阶段有自己的回调队列:

    1. timers:执行setTimeout、setInterval回调。
    2. pending callbacks:执行某些系统操作的回调(如TCP错误)。
    3. idle, prepare:libuv内部用。
    4. poll:最重要阶段!
      • 轮询I/O事件(epoll_wait / IOCP等)。
      • 如果有就绪事件,执行相关回调。
      • 如果没有,可能会阻塞等待(取决于是否有timer)。
    5. check:执行setImmediate回调。
    6. close callbacks:执行close事件回调。
  • 每个阶段执行完,会检查是否有process.nextTick队列,有则立即清空(microtask,比Promise还优先)。

  • 循环永不停止,除非无任何观察者/定时器/请求。

作者强调:poll阶段是异步I/O回调执行的主要场所

下面是现代Node事件循环阶段图(书里是早期版本,现在阶段基本一致,但有细微优化):

这一子节读完,你就知道为什么setTimeout不一定准时、为什么nextTick比Promise快了。