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

Node Express调用sendEmail发信后接口无响应问题求解

/signup 接口邮件发送成功但请求持续挂起无响应问题修复

问题代码

我针对 /signup 路由编写了如下处理方法:

const {sendEmail} = require("../lib/email");

exports.signup = async (req, res, next) => {
  try {
       //validate request and save user in DB
    
        let content = {
          activateLink: "link to activate email",
        };

      await sendEmail(email, "Verification OTP", "otp", content, next);

      res.status(201).json({
       message: "User created successfully !",
       userData: {
       _id: result._id.toString(),
        email: result.email,
       },
      });
  }
    catch (err) {
        if (!err.statusCode) {
          err.statusCode = 500;
        }
        next(err);
  }
};

邮件发送方法的实现代码如下:

const path = require("path");
const nodemailer = require("nodemailer");
const hbs = require("nodemailer-express-handlebars");
const viewPath = path.resolve(__dirname, "../templates/views");
const partialsPath = path.resolve(__dirname, "../templates/partials");
const config = require("../config");

exports.sendEmail = async (to, subject, viewName, content, next) => {
    try {
      const transporter = nodemailer.createTransport(config.mailConfig);
      const handlebarsOptions = {
        viewEngine: {
          extName: ".handlebars",
          layoutsDir: viewPath,
          defaultLayout: false,
          partialsDir: partialsPath,
        },
        viewPath: viewPath,
        extName: ".handlebars",
      };
  
      transporter.use("compile", hbs(handlebarsOptions));
  
      const mailOptions = {
        from: "noreply@admin.com", // Update from email
        to: to,
        subject: subject,
        template: viewName,
        context: content,
      };
  
      let info = await transporter.sendMail(mailOptions);
      console.log("Message sent: %s", info.messageId);
    } catch (e) {
      next(e);
    }
  };

故障现象

发起POST请求时,邮件可以正常发送给新注册用户,但Postman会持续处于等待状态,邮件发送完成后也无法收到接口返回的状态码和响应消息。判断是代码执行流程没有从sendEmail方法正常退出,即便在console.log("Message sent: %s", info.response);后添加return true;也无法解决问题。

故障原因

核心问题是错误地将Express的next回调传入了通用邮件工具函数,打乱了async/await的正常错误传播逻辑:

  • 工具函数sendEmail内部捕获到错误时,直接调用next(e)触发错误处理,但既不抛出错误也不终止函数执行,会导致外层await无法感知到错误发生,后续逻辑继续执行时会出现响应流程冲突
  • 把HTTP请求生命周期相关的next逻辑耦合到通用工具函数中,会导致函数返回状态不可控,部分场景下会出现既不执行成功响应、也不走到错误响应的挂死状态
  • 原写法每次发邮件都重新创建邮件传输实例、注册模板引擎插件,也会造成不必要的资源开销

修复方案

  1. 重构sendEmail工具方法,移除next参数,工具函数只负责邮件发送逻辑:成功时返回发送结果,失败时直接抛出错误,不要在工具函数内处理HTTP请求相关逻辑。同时可以把邮件传输实例、模板插件初始化移到模块级,复用实例提升性能:
const path = require("path");
const nodemailer = require("nodemailer");
const hbs = require("nodemailer-express-handlebars");
const viewPath = path.resolve(__dirname, "../templates/views");
const partialsPath = path.resolve(__dirname, "../templates/partials");
const config = require("../config");

// 模块级初始化邮件传输实例,复用连接
const transporter = nodemailer.createTransport(config.mailConfig);
const handlebarsOptions = {
  viewEngine: {
    extName: ".handlebars",
    layoutsDir: viewPath,
    defaultLayout: false,
    partialsDir: partialsPath,
  },
  viewPath: viewPath,
  extName: ".handlebars",
};
transporter.use("compile", hbs(handlebarsOptions));

exports.sendEmail = async (to, subject, viewName, content) => {
  const mailOptions = {
    from: "noreply@admin.com",
    to: to,
    subject: subject,
    template: viewName,
    context: content,
  };
  const info = await transporter.sendMail(mailOptions);
  console.log("Message sent: %s", info.messageId);
  return info;
};
  1. 修改路由处理逻辑,在路由层统一处理错误和响应,不再把next传给工具函数:
const {sendEmail} = require("../lib/email");

exports.signup = async (req, res, next) => {
  try {
       // 校验请求、存储用户到DB的逻辑
       // 注意确保email、result变量在此处正确赋值,不要漏定义
        const content = {
          activateLink: "link to activate email",
        };

      await sendEmail(email, "Verification OTP", "otp", content);

      res.status(201).json({
       message: "User created successfully !",
       userData: {
        _id: result._id.toString(),
        email: result.email,
       },
      });
  }
    catch (err) {
        if (!err.statusCode) {
          err.statusCode = 500;
        }
        // 所有错误统一在路由层交给全局错误中间件处理
        next(err);
  }
};
  1. 额外校验项:确认Express全局错误处理中间件是标准的四参数格式(err, req, res, next) => {},且内部一定会返回响应(比如调用res.status(err.statusCode).json(...)),不要出现既不发送响应也不调用next的情况,否则同样会导致请求挂起。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 03:39:35