为何Node.js Express服务器同一客户端同路由请求需等待前一个完成?
嘿,这个现象其实和Express本身的路由机制关系不大,核心问题出在Node.js的单线程事件循环模型,以及你/foo路由处理器里的代码逻辑上,我给你拆解明白:
先搞懂Node.js的核心:单线程+事件循环
Node.js的主线程是唯一处理JavaScript逻辑的线程,所有请求的处理器代码都在这个线程里跑。如果某个请求的处理器里有同步阻塞操作(比如耗时10秒的循环、fs.readFileSync这种同步IO、或者同步数据库查询),主线程会被死死占住,根本腾不出手处理后续的任何请求——不管是相同路由还是不同路由。
但你说访问/bar时控制台能立即输出日志,这说明你的/bar处理器是异步非阻塞的,而/foo的处理器大概率藏着阻塞逻辑或者串行处理的规则。
举个最典型的例子,假设你的代码是这样的:
const express = require('express'); const app = express(); // /foo的处理器有同步阻塞逻辑 app.get('/foo', (req, res) => { console.log('开始处理/foo请求'); // 模拟10秒的同步死循环,占住主线程 const endTime = Date.now() + 10000; while (Date.now() < endTime) {} console.log('/foo请求处理完成'); res.send('搞定!'); }); // /bar的处理器是异步非阻塞的 app.get('/bar', (req, res) => { console.log('开始处理/bar请求'); res.send('搞定!'); }); app.listen(3000);
这时候如果你先开一个标签页访问/foo,主线程会卡在while循环里。这时候再开标签页访问/bar,你会发现控制台并不会立即输出日志——得等第一个/foo的循环跑完才行。但你说的情况是/bar能立即输出,那大概率你的/foo是用了强制串行的异步逻辑,比如:
// 全局Promise链,让所有/foo请求串行执行 let currentTask = Promise.resolve(); app.get('/foo', (req, res) => { console.log('/foo请求进入队列'); // 把当前请求的逻辑挂到全局Promise链上,强制排队 currentTask = currentTask.then(() => { console.log('开始处理/foo请求'); // 模拟异步操作,但通过Promise链串行 return new Promise(resolve => setTimeout(resolve, 10000)); }).then(() => { res.send('搞定!'); }); }); app.get('/bar', (req, res) => { console.log('开始处理/bar请求'); res.send('搞定!'); });
这种情况下,多个/foo请求会因为全局的Promise链排队,而/bar不受影响,完美符合你描述的现象。
为什么你会觉得“所有请求都是异步处理”?
你说的“异步处理”是Node.js的非阻塞IO特性——当遇到数据库查询、文件读写这种IO操作时,Node.js会把操作扔给底层线程池,主线程继续处理其他请求。但如果你的处理器里是同步阻塞代码,或者是自己实现的串行逻辑,那主线程还是会被占住,或者请求被强制排队。
怎么验证和解决?
你可以检查下/foo的处理器代码:
- 有没有同步的耗时操作?如果有,改成异步版本(比如用
fs.readFile代替fs.readFileSync),或者把耗时计算放到Worker线程里。 - 有没有用全局变量、锁或者Promise链来强制串行处理?如果是业务需要串行那没问题,要是误操作导致的,调整逻辑就能让请求并行处理了。
总结
Express本身不会对相同路由的请求做特殊排队,出现这种现象的核心原因是你的/foo路由处理器要么阻塞了Node.js主线程,要么实现了串行处理的逻辑,而/bar的处理器是异步非阻塞的,所以能被立即处理。
内容的提问来源于stack exchange,提问作者exNihilo

