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

Express API中无法捕获并处理Mongoose/MongoDB错误

解决MongoDB重复键错误无法捕获及前端提示问题

问题核心

想要让前端感知到重复提交ID的验证错误,但代码中的异常捕获逻辑完全失效,MongoDB的重复键错误没进入自定义的catch块,最终触发了未处理的Promise拒绝警告,无法将错误信息反馈给前端。

错误原因

你在await finishedjob.save()后链式调用了.catch(),这个操作会吞噬Promise的拒绝状态,把它转换成已解决的Promise,导致外层的try/catch无法捕获到错误,最终触发全局的未处理Promise拒绝提示。

修复方案

方案1:移除链式.catch(),用try/catch统一捕获

删掉.catch(),让await抛出的错误直接进入外层try/catch块,这样就能正常捕获重复键错误,再给前端返回对应提示:

exports.createFinishedJob = async (req, res) => {
    try {
        console.log('createFinishedJob req.body', req.body);
        const finishedjob = new FinishedJob();
        if (req.body.jobSubmissionId) { finishedjob.jobSubmissionId = req.body.jobSubmissionId };
        if (req.body.customerId) { finishedjob.customerId = new ObjectId(req.body.customerId) };
        finishedjob.finishedjob = req.body.finishedjob;
        if (req.body.other) { finishedjob.other = req.body.other };
        console.log('createFinishedJob', finishedjob);

        // 移除.catch(),让错误进入try/catch
        await finishedjob.save();
        // 保存成功返回数据
        return res.status(201).json(finishedjob);
    } catch (err) {
        console.error("createFinishedJob error", err);
        // 判断是否为MongoDB重复键错误
        if (err.code === 11000) {
            return res.status(409).json({
                message: `提交ID ${err.keyValue.jobSubmissionId} 已存在,请勿重复提交`
            });
        }
        // 其他错误返回通用提示
        return res.status(500).json({ message: "创建失败,请稍后重试" });
    }
};

方案2:保留.catch()但重新抛出错误

如果需要在链式调用里做日志,可以在.catch()处理后重新抛出错误,让外层try/catch捕获:

exports.createFinishedJob = async (req, res) => {
    try {
        console.log('createFinishedJob req.body', req.body);
        const finishedjob = new FinishedJob();
        if (req.body.jobSubmissionId) { finishedjob.jobSubmissionId = req.body.jobSubmissionId };
        if (req.body.customerId) { finishedjob.customerId = new ObjectId(req.body.customerId) };
        finishedjob.finishedjob = req.body.finishedjob;
        if (req.body.other) { finishedjob.other = req.body.other };
        console.log('createFinishedJob', finishedjob);

        try {
            await finishedjob.save()
                .catch(err => {
                    console.log("HALLO err", err);
                    // 重新抛出错误,让外层try/catch捕获
                    throw err;
                });
            return res.status(201).json(finishedjob);
        } catch (err) {
            console.error("createFinishedJob error", err);
            if (err.code === 11000) {
                return res.status(409).json({
                    message: `提交ID ${err.keyValue.jobSubmissionId} 已存在,请勿重复提交`
                });
            }
            return res.status(500).json({ message: "创建失败,请稍后重试" });
        }
    } catch (err) {
        console.error("Something went wrong")
        console.error(err)
        return res.status(500).json({ message: "系统异常,请稍后重试" });
    }
};

额外说明

Mongoose的unique: true是用来创建MongoDB唯一索引的,不属于Schema验证逻辑,所以重复键错误是MongoDB层面抛出的,需要通过错误码11000判断。如果要在Schema层面做前置验证,可以添加自定义验证器:

jobSubmissionId:{
    type: String,
    unique: true,
    index: true,
    validate: {
        validator: async function(value) {
            const count = await this.constructor.countDocuments({ jobSubmissionId: value });
            return count === 0;
        },
        message: props => `提交ID ${props.value} 已存在`
    }
}

注意:这种前置验证在高并发场景下可能存在竞态问题,最终还是要依赖MongoDB的唯一索引保证数据一致性,建议两种方式结合使用。

内容的提问来源于stack exchange,提问作者urfx

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:35:56