Node+Express+Mongo注册Promise逻辑异常:HTTP头重复发送错误排查
解决邮箱重复校验时的Promise执行顺序与响应重复发送问题
我来帮你拆解下问题根源,以及对应的修复方案:
核心问题1:then()里的插入操作被立即执行了
你在then()中直接写了tatuadorDB.insertTatuador(...),这相当于不管前面的Promise是成功还是失败,这个插入函数都会立刻触发——因为你没有把它包裹成回调函数,而是直接调用了它。这就是为什么即使触发了reject,插入操作还是会跑,之后又进入catch发送响应,导致重复发送响应头,抛出ERR_HTTP_HEADERS_SENT错误。
核心问题2:existsTatuador的Promise逻辑不完整
你的existsTatuador函数只有在找到重复邮箱时才会reject,但如果遍历完所有文档都没找到重复,Promise永远不会resolve,这会导致请求一直挂着,没有任何响应返回给客户端。而且遍历所有文档来判断重复的方式效率极低,MongoDB本身就支持直接查询是否存在匹配文档。
修复后的完整代码
第一步:重构existsTatuador函数
改成直接用MongoDB的查询判断邮箱是否存在,同时完善Promise的resolve逻辑:
const existsTatuador = (db, tatuador) => { return new Promise((resolve, reject) => { // 直接查询匹配邮箱的文档,效率远高于遍历全部 tatuadorDB.findTatuadorByCorreo(db, tatuador.correo, (err, doc) => { if (err) { reject(Error("数据库查询出错:" + err.message)); return; } if (doc) { // 找到重复邮箱,触发reject reject(Error("该邮箱已被注册")); } else { // 邮箱未被使用,resolve继续执行插入 resolve(); } }); }); }; // 需要在tatuadorDB中新增一个按邮箱查询的方法: // const findTatuadorByCorreo = (db, correo, callback) => { // db.collection('tatuadores').findOne({ correo: correo }, callback); // };
第二步:修复路由里的Promise调用逻辑
把插入操作包裹成回调函数传给then(),这样只有当Promise resolve(即邮箱未重复)时才会执行插入:
router.post('/', function(req, res, next) { mongo.getDatabase(db => { tatuadorLogic.existsTatuador(db, req.body) .then(() => { // 仅邮箱校验通过时才执行插入 tatuadorDB.insertTatuador(db, data => { console.log("开始创建纹身师:"); console.log(req.body); res.status(201).send(req.body); // 用201状态码表示创建成功更规范 }, req.body); }) .catch((err) => { console.log(err); res.status(400).send(err.message); // 用400状态码表示客户端错误 }); }); });
额外优化建议
- 用async/await简化代码:Promise链式调用可以用async/await替代,可读性更强:
router.post('/', async function(req, res, next) { try { const db = await mongo.getDatabase(); // 建议把getDatabase改成返回Promise的版本 await tatuadorLogic.existsTatuador(db, req.body); await tatuadorDB.insertTatuador(db, req.body); // 同样把insert改成返回Promise的版本 console.log("开始创建纹身师:"); console.log(req.body); res.status(201).send(req.body); } catch (err) { console.log(err); res.status(400).send(err.message); } });
- 数据库操作Promise化:把所有基于回调的数据库操作都改成返回Promise的形式,更符合现代Node.js异步编程风格,避免回调地狱。
内容的提问来源于stack exchange,提问作者Leonardo Mejia Bustos
相关产品推荐
相关产品推荐

