You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 01:57:00