Express.js中app.post内使用await并外部获取请求体时多次请求出现冻结的问题
问题分析与解决方案
你的代码出现冻结的核心问题在于每次调用receivePost都会给同一个url注册一个新的POST路由处理器,而不是替换已有的。当发起两次及以上请求时,同一个URL会触发多个注册的处理器,导致Promise逻辑混乱、请求处理流程冲突,最终引发冻结。
另外还有两个易忽略的问题:
- 不需要手动调用
req.end(),Express在调用res.send()后会自动完成请求的收尾工作,手动调用反而可能干扰正常流程。 - 你的Promise只会
resolve第一次请求的req.body,后续请求触发的resolve不会有任何效果——因为Promise的状态一旦改变就无法再修改。
修正方案:避免重复注册路由,改用事件驱动处理多次请求
我们可以通过Node.js的EventEmitter来实现多次请求的数据传递,确保每个URL只注册一次路由处理器,后续请求通过事件通知的方式传递数据。
修正后的代码示例
const { EventEmitter } = require('events'); class YourClass { constructor() { this.app = /* 你的Express app实例 */; this.routeEvents = new EventEmitter(); // 维护已注册路由的集合,避免重复添加处理器 this.registeredRoutes = new Set(); } receivePost(url, response) { // 检查路由是否已注册,未注册才添加处理器 if (!this.registeredRoutes.has(url)) { this.app.post(url, async (req, res) => { // 先返回响应给客户端 res.send(response); // 通过事件发送请求体数据给外部 this.routeEvents.emit(`post:${url}`, req.body); }); this.registeredRoutes.add(url); } // 返回Promise用于获取单次请求数据;若需多次获取,可直接监听对应事件 return new Promise((resolve) => { const handler = (body) => { resolve(body); // 移除监听器,避免内存泄漏 this.routeEvents.off(`post:${url}`, handler); }; this.routeEvents.once(`post:${url}`, handler); }); } }
方案说明
- 避免重复注册路由:通过
registeredRoutes集合记录已注册的URL,确保每个URL只绑定一次POST处理逻辑。 - 事件驱动传递数据:使用
EventEmitter,每次收到请求时触发对应URL的事件,外部通过监听事件(或返回的Promise)获取请求数据。 - 规范请求收尾:移除了不必要的
req.end(),依赖Express自身的请求处理流程完成收尾。
为什么重启服务器没用?
因为每次调用receivePost都会在当前的app实例上添加新的路由处理器,即使重启服务器,如果你的代码逻辑还是会重复调用receivePost注册路由,问题依然会出现。只有从根源上避免重复注册路由,才能彻底解决这个问题。
内容的提问来源于stack exchange,提问作者Алексей Алексеев
相关产品推荐
相关产品推荐

