注册接口返回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本身禁止这类并行操作:
generateAuthToken是异步方法,内部已经实现了await user.save()逻辑,用来把新生成的JWT写入用户的tokens数组- 路由里调用
generateAuthToken()时没有加await,方法内部的save操作还在pending状态,代码就直接往下执行,触发了try块里写的第二个await user.save() - 两个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
相关产品推荐
相关产品推荐

