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

Express错误处理最佳实践与全局异常捕获中间件实现方法

先解释你示例代码的问题

你的判断完全正确。Express 中间件按注册顺序依次执行,只要前面的路由或中间件已经通过res.send()等方法结束了响应、且没有主动调用next(),后续注册的中间件就不会执行。你写的最后一个中间件本质是兜底逻辑,只有当前面所有路由都没匹配到请求、或者前置逻辑主动放行时才会触发,正常命中路由返回响应的场景下确实不会执行。

Express 错误处理的标准最佳实践

Express 有专门的错误处理中间件规范,不需要你在每个路由里重复写try/catch,核心规则和实现步骤如下:

  • 错误处理中间件必须固定包含4个参数(err, req, res, next),少一个参数Express就不会把它识别为错误处理组件,必须注册在所有普通中间件、所有路由的最后面。
    注意:哪怕你在中间件里用不到next参数,也必须在形参里写全4个参数,否则Express会把它当成普通中间件处理,接不到错误对象
  • 同步代码抛出的错误,Express 会自动捕获并传递到错误处理中间件,不需要手动处理。
  • 异步代码(包括async/await、回调风格的异步逻辑)在Express 4.x版本中不会被自动捕获,你只需要写一个通用的高阶包装函数,就不用每个路由重复写捕获逻辑:
// 异步路由通用包装器,所有async路由直接套这个就行
const asyncWrap = (routeHandler) => (req, res, next) => {
  Promise.resolve(routeHandler(req, res, next)).catch(next)
}

// 使用示例
app.get('/user', asyncWrap(async (req, res) => {
  // 这里不管是await异步操作抛错,还是同步throw错误,都会自动传到全局错误中间件
  const user = await db.User.findById(req.query.id)
  res.send(user)
}))

如果是用Express 5.x版本,异步错误已经做了自动捕获,连这个包装器都不需要,直接写async路由就行,抛错会自动进错误中间件。

  • 在所有路由之后、错误处理中间件之前,加一个404兜底中间件,把未匹配到路由的请求统一转成错误传给后续处理:
app.use((req, res, next) => {
  const notFoundErr = new Error(`路径 ${req.method} ${req.path} 不存在`)
  notFoundErr.statusCode = 404
  next(notFoundErr)
})
  • 最后写全局错误处理中间件,你需要的告警逻辑、统一错误返回都放在这里,所有错误都会汇总到这:
app.use((err, req, res, next) => {
  // 统一告警逻辑:发邮件、推监控webhook、打错误日志全写在这,只需要写一次
  console.error('[应用错误]', err)
  // 只有5xx类的服务端错误触发告警,4xx类的客户端请求错误不需要告警
  if (!err.statusCode || err.statusCode >= 500) {
    // 替换成你自己的邮件、告警推送逻辑
    sendCrashAlert(err)
  }

  // 统一返回错误响应给客户端
  const status = err.statusCode || 500
  res.status(status).json({
    msg: process.env.NODE_ENV === 'production' 
      ? status >=500 ? '服务异常,请稍后重试' : err.message
      : err.message,
    // 开发环境可以返回错误栈方便排查,生产环境不要返回
    stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
  })
})
  • 额外补一层进程级的错误监听,兜底捕获完全没被Express接住的异常,避免应用直接崩溃没告警:
// 捕获全局未处理的同步异常
process.on('uncaughtException', (err) => {
  console.error('[全局未捕获异常]', err)
  sendCrashAlert(err)
  // 这类异常出现后建议退出进程,用pm2、k8s等工具自动重启服务,避免内存泄漏等问题
  process.exit(1)
})

// 捕获全局未处理的Promise异常
process.on('unhandledRejection', (err) => {
  console.error('[未处理Promise异常]', err)
  sendCrashAlert(err)
})
对你现有方案的评价

你现在在每个路由里单独写try/catch、单独触发告警的方式能跑,但完全不是最优解,问题很明显:

  • 重复代码太多,每加一个路由就要写一遍相同的捕获、告警、返回逻辑
  • 非常容易漏写,只要有一个异步路由没包try/catch,抛错就会直接打挂进程,告警根本触发不了
  • 错误返回格式很难统一,不同路由可能返回不同结构的错误信息,前端对接成本高
  • 后续要改告警规则、改返回格式的时候,要改所有路由的代码,维护成本极高

用上面的全局错误处理方案,你可以把所有路由里的try/catch全删掉,所有错误都会自动汇总到全局中间件,告警逻辑只需要维护一份,也不会出现漏捕获的情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 20:39:30