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

注册接口返回400 Bad Request但数据入库,报Mongoose并行save错误

问题说明
  • 实现目标:完成应用新用户注册逻辑,将用户提交的注册数据存入数据库
  • 异常现象:提交注册请求后,用户数据实际已成功写入数据库,但接口返回400 Bad Request状态码,控制台抛出错误:Can't save() the same doc multiple times in parallel
  • 复现条件:在路由逻辑中添加const token=user.generateAuthToken()代码行,且在成功响应中返回{user,token}后,问题可稳定复现。
关联代码

userSchema 生成JWT令牌实例方法

userSchema.methods.generateAuthToken = async function(){
    const user = this;
    const token = jwt.sign({_id:user._id.toString()}, 'helloworld');
    user.tokens = user.tokens.concat({token});
    await user.save();
    return token;
}

POST /users 注册接口处理逻辑

router.post('/users',async(req,res)=>{
    const user = new User(req.body);
    const token = user.generateAuthToken();
    try{
        await user.save();
        res.status(201).send({user,token});
    }
    catch(err){
        console.log(err);
        res.status(400).send(err);
    }
})
问题根因

报错核心是同一个Mongoose文档实例触发了并行save操作,Mongoose本身禁止这类并行操作:

  1. generateAuthToken是异步方法,内部已经实现了await user.save()逻辑,用来把新生成的JWT写入用户的tokens数组
  2. 路由里调用generateAuthToken()时没有加await,方法内部的save操作还在pending状态,代码就直接往下执行,触发了try块里写的第二个await user.save()
  3. 两个save操作最终都执行完成,所以数据库里能看到正确写入的用户数据,但并行save触发了Mongoose的内置校验,抛出错误进入catch分支,最终接口返回400。
    除此之外,没加await的情况下token变量拿到的是pending状态的Promise,不是实际的JWT字符串,就算不报错,返回给前端的token值也是无效的。
修复方案

删掉路由里冗余的user.save()调用,调用generateAuthToken时加上await等待异步执行完成即可,修改后的路由代码如下:

router.post('/users',async(req,res)=>{
    const user = new User(req.body);
    try{
        const token = await user.generateAuthToken();
        res.status(201).send({user,token});
    }
    catch(err){
        console.log(err);
        res.status(400).send(err);
    }
})

这种写法只需要执行一次数据库写入,性能最优。如果要拆分保存逻辑,也可以先执行await user.save()保存基础用户信息,再调用await user.generateAuthToken()写入token,但会触发两次数据库写操作,没有必要。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 04:45:37