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

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状态码表示客户端错误
      });
  });
});

额外优化建议

  1. 用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);
  }
});
  1. 数据库操作Promise化:把所有基于回调的数据库操作都改成返回Promise的形式,更符合现代Node.js异步编程风格,避免回调地狱。

内容的提问来源于stack exchange,提问作者Leonardo Mejia Bustos

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 18:32:31