好,我们进入第三章的核心重头戏: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实现的。整个过程可以概括为四个阶段:
- 初始化事件循环(Node启动时libuv创建loop)。
- 用户调用异步API(如fs.readFile),JS层创建回调。
- C++层封装请求对象,投递给libuv(可能进线程池或直接系统调用)。
- I/O完成后,libuv把回调推入事件队列。
- 事件循环轮询到相应阶段,执行JS回调。
现在我们从第一个子小节开始。
3.3.1 事件循环
这是Node的“心脏”——Event Loop。
-
Node的事件循环由libuv实现,和浏览器的Event Loop类似,但更强大(支持文件I/O、子进程等)。
-
事件循环分为多个阶段(phases),每个阶段有自己的回调队列:
- timers:执行setTimeout、setInterval回调。
- pending callbacks:执行某些系统操作的回调(如TCP错误)。
- idle, prepare:libuv内部用。
- poll:最重要阶段!
- 轮询I/O事件(epoll_wait / IOCP等)。
- 如果有就绪事件,执行相关回调。
- 如果没有,可能会阻塞等待(取决于是否有timer)。
- check:执行setImmediate回调。
- close callbacks:执行close事件回调。
-
每个阶段执行完,会检查是否有process.nextTick队列,有则立即清空(microtask,比Promise还优先)。
-
循环永不停止,除非无任何观察者/定时器/请求。
作者强调:poll阶段是异步I/O回调执行的主要场所。
下面是现代Node事件循环阶段图(书里是早期版本,现在阶段基本一致,但有细微优化):
这一子节读完,你就知道为什么setTimeout不一定准时、为什么nextTick比Promise快了。