I/O 阶段到底是什么?
在事件循环模型中,I/O 阶段承担着处理外部 I/O 事件的职责。这个阶段的核心在于监听文件描述符上的可读/可写事件,触发对应的回调,并将就绪的任务有序地送入执行队列。理解I/O阶段的回调队列、触发时机与执行顺序,有助于把握异步操作的性能边界和响应时间表现。
与定时器阶段或微任务队列相比,I/O 阶段的触发往往来自外部事件:磁盘读取完成、网络套接字可读可写等。当系统检测到就绪的 I/O 事件时,事件循环将相应的 I/O 回调加入队列并在本阶段逐个执行。如果没有就绪事件,poll(轮询)会进入阻塞模式,直到有事件到来或达到超时阈值。
下面给出一个简化示例,说明 I/O 阶段如何工作。
const fs = require('fs');
fs.readFile('big.txt', 'utf8', (err, data) => {if (err) throw err;console.log('read complete', data.length);
});
console.log('start reading');

通过上面的代码可以看到:主线是在发起 I/O 请求之后继续往下执行,实际完成后的回调会在 I/O 阶段被调度执行。微任务队列(Promise、process.nextTick)在每个阶段结束时被清空并立即执行,确保逻辑上的后置任务不会无限延迟。
综合来看,I/O 阶段的关键在于对外部事件的响应、对就绪回调的调度以及与其他阶段的协同执行,以实现对高并发 I/O 的高效处理。
事件循环的关键环节全解析与实战要点
1) 定时器阶段(Timers)
Timers 阶段负责执行通过 setTimeout 与 setInterval 安排的回调。需要注意的是,设定的最小时间单位和实际到期时间会受系统时间分辨率、任务队列拥挤程度等因素影响,实际执行往往存在轻微延迟。
当有多个定时器同时到期时,回调的执行顺序遵循在队列中的先后顺序,随后进入下一阶段继续处理。如果定时器回调执行时间较长,可能会阻塞后续阶段的任务,影响响应性。
console.log('start');
setTimeout(() => {console.log('timeout 1');
}, 0);
setTimeout(() => {console.log('timeout 2');
}, 0);
console.log('end');
在上述示例中,尽管两条定时任务都设为 0 毫秒,但实际输出顺序仍然受到事件循环队列的调度影响,定时器回调的执行时机是一个全局调度过程。
2) I/O 阶段(Poll 阶段)
Poll 阶段是事件循环中最具 I/O 命意的阶段,用于等待并分发就绪的 I/O 事件,如网络数据到达、磁盘读取完成等。系统会将就绪的回调放入待执行队列,逐个执行。
常见的 I/O 场景包括网络请求、流式数据读取、子进程通信等。如果没有就绪事件,poll 可以阻塞等待,直到超时、或有事件到来,这对于高并发场景的吞吐至关重要。
const https = require('https');
https.get('https://example.com', (res) => {res.on('data', (chunk) => {// 处理数据});res.on('end', () => {console.log('响应结束');});
}).on('error', (e) => {console.error(e);
});
上述示例展示了一个典型的 I/O 回调链路:网络请求的数据到来时,在 Poll 阶段触发对应的回调,随后将数据块进行处理。如果没有新数据,poll 会进入等待状态,直到有数据到来,保证资源使用的高效性。
3) 结束阶段与回调清理(Check/Close 回调与 Immediate)
Check 阶段通过 setImmediate 调度的回调在本阶段执行,通常用于在 I/O 阶段完成后快速处理一些后续操作,从而提供更灵活的交互点。它为短期任务提供了一个相对确定的执行时机。
此外,Close 回调(如 socket 的 close 事件)会在专门的阶段触发,用于清理资源、释放句柄等操作。对资源清理的及时性,往往影响应用的稳定性与内存/句柄的利用率。
const net = require('net');
const server = net.createServer((socket) => {socket.on('end', () => {console.log('client disconnected');});
});
server.listen(0, () => {setImmediate(() => console.log('setImmediate 触发'));
});
通过以上代码,可以看到在 I/O 阶段完成后,setImmediate 提供了一个快速接管的入口,使开发者能够更灵活地控制后续工作流的时序。对等待执行的回调的分层设计,是提升应用响应性的关键。


