Node.js事件循环疑问:高并发下Poll与Check阶段执行顺序
原问题
假设我的Web应用非常受欢迎,Node.js服务器每秒处理数千请求,每个请求回调都会调用setImmediate(cb)。我认为setImmediate回调没有机会执行,因为事件循环因Poll阶段每秒都有大量请求回调,无法进入Check阶段,这是否正确?我进行了如下模拟:
客户端代码
for(let i = 1; i <= 50; i++) { const fetchData = async () => { const data = await fetch('http://127.0.0.1:3000'); console.log(data + ' ' + i); } fetchData() }
服务端代码
const express = require('express'); const fs = require('fs'); const cors = require('cors'); const app = express(); app.use(cors()) let num = 0; app.get('/', (req, res) => { num = num + 1; console.log(`the request number is: ${num}`); res.status(200).send('hello from the server'); setImmediate(() => { console.log('hello i am from setImmediate ' + num ) }) }) app.listen(3000, () => { console.log('server is listening') })
Node.js文档说明,事件循环进入Poll阶段会执行回调队列中所有回调,队列清空后才进入Check阶段执行setImmediate回调。但我的模拟中,发送50个请求后,本该有50个请求回调在Poll阶段,实际却是完成一个请求回调就执行对应的setImmediate回调,再处理下一个请求回调,这与文档描述不符,求解释。
解答
核心结论
你的初始假设错误——高并发场景下,setImmediate回调不会因为Poll阶段持续有请求回调而永远无法执行。
事件循环Poll阶段的实际运行逻辑
Node.js文档的描述是简化版的理想状态,实际运行受libuv调度机制限制:
- Poll阶段不会一次性处理所有堆积的回调,每次事件循环迭代会处理一批回调(数量由libuv内部阈值控制),防止单个阶段长时间占用事件循环导致其他阶段饥饿。
- 当Poll队列处理到一定程度,或者有
setImmediate回调待执行、定时器到期时,事件循环会退出Poll阶段,依次进入Check、Timers等阶段处理对应任务。
模拟现象的原因
你看到的“逐个执行请求回调后立刻跑setImmediate”,主要是两个因素:
- 浏览器并发限制:浏览器对同一域名的并发请求有默认限制(通常6-8个),50个
fetch请求是分批发送到服务器的,并非一次性全部进入Poll队列。每处理完一个请求回调,事件循环会自然推进到Check阶段执行对应的setImmediate,再回到Poll阶段处理下一批请求。 - 请求回调的执行粒度:每个HTTP请求的回调是独立的事件循环任务,单个回调执行完毕后,当前迭代会继续走完全部阶段,而非停留在Poll阶段等待所有请求。
验证高并发场景的正确方式
如果要模拟“大量请求回调堆积在Poll阶段”,应该用压测工具(如ab -n 1000 -c 100 http://127.0.0.1:3000),或者在服务端内部批量生成回调:
app.get('/batch', (req, res) => { for (let i = 0; i < 100; i++) { // 用process.nextTick模拟Poll阶段的回调堆积 process.nextTick(() => { console.log(`Poll callback ${i}`); setImmediate(() => console.log(`setImmediate ${i}`)); }); } res.send('done'); });
此时你会看到:先批量执行完所有Poll阶段的回调,再一次性执行所有setImmediate回调,这才符合文档描述的理想场景。
内容的提问来源于stack exchange,提问作者Junaid Arshad
相关产品推荐
相关产品推荐

