如何用ExpressJS异步路由处理器处理并发请求?
问题分析与解决方案
一、Node.js + Express 中 await 的资源释放逻辑
Node.js 采用单主线程+事件循环模型,Express 的所有请求处理逻辑都在主线程上执行:
- 当路由处理器执行到
await xxx()时,若xxx()是真正的异步IO操作(比如数据库查询、HTTP请求、文件读写),主线程会将该IO任务交给操作系统内核处理,随即释放主线程去处理下一个请求。等内核完成IO后,会把结果放入事件队列,主线程空闲时再回来执行后续代码。 - 但如果
xxx()是CPU密集型同步代码(比如复杂计算、循环处理海量数据),哪怕用await包裹(比如await new Promise(resolve => { 同步密集代码; resolve() })),主线程也会被完全占用,必须等这段代码执行完毕才能处理其他请求——这就是你看到第二个请求需等待第一个完成才进入处理器的核心原因。
你的情况大概率是 timeConsumingFunction 本质是CPU密集的同步任务,而非真正的异步IO,导致主线程阻塞。
二、让多个请求同时进入路由处理器的解决方法
1. 按任务类型针对性处理
- IO密集型任务:确保
timeConsumingFunction是真正的异步IO实现(比如使用原生异步API、返回合法Promise),这种场景下await会自动释放主线程,后续请求能立即进入处理器。 - CPU密集型任务:将任务移至Node.js的
Worker Threads(工作线程)或子进程中执行,避免阻塞主线程:
示例代码(基于Worker Threads):// worker.js const { parentPort } = require('node:worker_threads'); parentPort.on('message', (data) => { // 此处执行CPU密集任务 const result = heavyComputation(data); parentPort.postMessage(result); }); // 路由处理器 export const handler: Handler = async (req, res) => { logger.info("reached inside handler", { data: { time: new Date().toISOString() } }); let results; if(someConditions){ // 创建工作线程执行耗时任务 const worker = new Worker('./worker.js'); worker.postMessage(req.body); // 传递任务所需数据 results = await new Promise((resolve, reject) => { worker.on('message', resolve); worker.on('error', reject); }); worker.terminate(); } else{ results = await fastFunction(...); } return res.status(200).json({ results }); };
2. 排查其他阻塞点
检查路由前的全局中间件是否存在同步长耗时操作(比如复杂计算、同步文件读取),这类代码同样会阻塞主线程,导致后续请求无法及时进入处理器。
三、快速验证任务类型的方法
在 timeConsumingFunction 中添加打印,判断是否为同步阻塞:
async function timeConsumingFunction() { console.log('开始执行耗时任务'); // 模拟CPU密集同步任务 for(let i=0; i<1e8; i++){} console.log('结束执行耗时任务'); return '结果'; }
如果调用时这两行打印连续输出,中间没有其他请求的日志,说明是同步阻塞;若中间能看到其他请求的日志,则是真正的异步操作。
内容的提问来源于stack exchange,提问作者vakarami
相关产品推荐
相关产品推荐

