Node.js Express过滤器异步Redis调用引发Promise.all并行接口404问题
问题核心原因
你的自定义过滤器链存在请求上下文共享竞态问题:
你将所有请求的执行状态(currentFunction、req、res、next等)都存储在全局共享的self.o对象中,当并发请求进入时(比如前端通过Promise.all同时发起2个API调用),后到达的请求调用run方法时会直接覆盖self.o内的所有属性。第一个请求在await Redis异步操作结束后,再读取self.o时拿到的已经是第二个请求的上下文,会导致过滤器链执行逻辑错乱,无法正确调用next进入对应API路由,最终返回404。单请求场景下没有并发覆盖问题,所以运行正常。
修复方案
1. 改造过滤器链实现请求上下文隔离
删掉全局共享的self.o对象,每次请求执行时创建独立的上下文实例,避免多个请求互相串数据:
// 修改run方法,每次生成独立上下文 run: function (req, res, next) { // 基于初始化options生成当前请求专属的上下文,不共享全局对象 const context = Object.assign({}, self.o || {}, { req: req, res: res, next: next, doNext: true, currentFunction: 0 }); if (context.next && context.currentFunction < self.filterList.length) { self.runNext(context, req, res); } }, // 修改runNext方法,使用传入的上下文而非全局self.o runNext: function (context, req, res) { if (context.next && context.currentFunction < self.filterList.length) { self.filterList[context.currentFunction](context, req, res, function(){ context.currentFunction++; self.runNext(context, req, res); }); } else { self.complete(context); } }, // 修改complete方法,使用传入的上下文 complete: function (context) { if (context.doNext) context.next(); }, // 可删除全局的o属性定义,避免后续误用
2. 补全异步异常兜底逻辑
你的过滤器是async函数,若Redis调用、JWT校验等逻辑抛出未捕获的异常,会导致callback永远不会执行,请求挂起后也会触发异常,需添加全局try/catch兜底:
module.exports = async (op, req, res, callback)=> { try { // 原有过滤器逻辑全部放到try块内 let jwtObj; var o = op || {}; if(req.url !== `/${app_name}/login/authenticate` && req.url !== `/${app_name}/login/authenticateForClient` && req.url !== `/${app_name}/getCsrfToken` && req.url !== `/${app_name}/adfs/postResponse` && req.url !== `/${app_name}/refreshAllRuleCategories/refreshDataByRunAllRuleCategory` && req.url !== `/${app_name}/assignWorkflowAndSurvey/assignWorkflowAndSurveyToUncompletedCases`) { try { jwtObj = jwtUtil.jwtVerify(req.cookies[jwt_name]); } catch (e) { responseHandler(res, {code: 308}, null); return callback(); // 返回错误后直接执行callback,终止后续逻辑 } if (jwtObj && jwtObj.dbUrl) { let login_iat = await getLoginiat(jwtObj.uGuid); if (jwtObj.iat != login_iat) { res.err[309] = codeMapping[309]; responseHandler(res, {code: 309}, null); return callback(); // 返回错误后直接终止 } let dbConnStr = getDatabaseConnectionString(); if(db && dbConnStr && dbConnStr != jwtObj.dbUrl){ await setDataConnections(jwtObj.dbUrl) } callback(); }else{ res.err[301] = codeMapping[301]; responseHandler(res, {code: 301}, null); callback(); } } else { callback(); } } catch (e) { console.error('过滤器执行异常:', e); responseHandler(res, {code: 500}, null); callback(); } }
3. 可选优化项
- 给Redis查询添加超时逻辑,避免Redis服务异常时请求长时间挂起:
function getLoginiat(uguid) { return Promise.race([ redisClient.get(uguid), new Promise((_, reject) => setTimeout(() => reject(new Error('Redis查询超时')), 3000)) ]); }
- 确认
responseHandler方法会调用res.end/res.json结束响应,避免响应结束后继续执行逻辑导致重复响应报错。
内容的提问来源于stack exchange,提问作者atnlantics
相关产品推荐
相关产品推荐

