为何return后代码仍执行?Mongoose唯一校验引发响应头错误
我之前也碰到过一模一样的问题,咱们先把根源说透,再给你靠谱的修复方案:
错误的核心原因
你遇到的这个报错,本质是Node.js异步回调的执行顺序问题。
newUser.save()是个异步数据库操作——它不会卡住等待数据库返回结果,而是会立刻继续执行后面的代码。所以当你调用save()之后,下面的console.log("here")、req.session.user = newUser、res.redirect('/newpost')会先一步执行,这时候已经给客户端发送了重定向的响应(响应头已经设置完毕)。
等数据库的异步操作完成、触发回调函数的时候,你再执行return res.status(500).send(...)就晚了——响应已经发出去了,没法再修改它的头部或者发送新的响应,自然就抛出了Can't set headers after they are sent的错误。
你以为的“return之后下面的代码还在执行”其实是个误解:return只是终止了回调函数内部的代码,但回调函数外面的代码,早在回调触发之前就已经跑完了。
直接修复方案
把所有依赖于save操作结果的代码,全部放到save的回调函数里面,确保只有在数据库操作完成后,才执行对应的响应逻辑:
router.post('/signup', function (req, res) { var name = req.body.name; var email = req.body.email; var password = req.body.password; var verify = req.body.verify; if (!name || !email || !password || !verify || password != verify) { var passwordError; if (password != verify) passwordError = 'Password are not matching'; res.render('signup.html', { 'Error': 'Invalid Details', 'name': name, 'email': email, 'passwordError': passwordError }); return; } var newUser = User( { name: name, email: email }); newUser.setPassword(password); newUser.save(function (err) { if (err) { console.log(err); return res.status(500).send({ success: false, message: 'User already exists' }); } // 所有保存成功后要执行的逻辑,都移到这里 console.log("here"); req.session.user = newUser; console.log(newUser); res.redirect('/newpost'); }); });
这样修改后,只有当save操作成功(没有错误)时,才会执行设置session和重定向的代码;如果有重复用户的错误,就直接返回错误响应,不会再触发后续的响应操作。
更优雅的优化:用async/await替代回调
如果你觉得回调嵌套看着麻烦,可以用ES6的async/await语法,让代码的执行顺序更直观,避免回调地狱:
router.post('/signup', async function (req, res) { var name = req.body.name; var email = req.body.email; var password = req.body.password; var verify = req.body.verify; if (!name || !email || !password || !verify || password != verify) { var passwordError; if (password != verify) passwordError = 'Password are not matching'; return res.render('signup.html', { 'Error': 'Invalid Details', 'name': name, 'email': email, 'passwordError': passwordError }); } try { var newUser = User( { name: name, email: email }); newUser.setPassword(password); // 等待数据库保存操作完成 await newUser.save(); console.log("here"); req.session.user = newUser; console.log(newUser); res.redirect('/newpost'); } catch (err) { console.log(err); return res.status(500).send({ success: false, message: 'User already exists' }); } });
用async/await后,代码会等待newUser.save()完成后再继续执行后续逻辑,错误处理也用更清晰的try/catch结构,可读性会好很多。
内容的提问来源于stack exchange,提问作者Dipendra bhatt

