Express 错误处理模式完全指南

本文系统讲解 Express 错误处理:同步/异步错误捕获、全局错误中间件、自定义错误类、AbortController 请求取消、限流保护与日志集中化方案。

最佳实践
应用接口与数据存储连接插画

Express 错误处理的骨架是四参数错误中间件 (err, req, res, next)——所有路由抛出的错误最终汇聚于此统一响应。配套纪律:异步路由必须 next(err) 传递(或升级 Express 5)、自定义错误类携带状态码、uncaughtException 只用于记录后优雅退出、错误日志集中采集告警。

先分清两类错误

  • 操作错误(Operational):外部条件导致的预期内失败——数据库连不上、第三方超时、用户输入非法。这类该被捕获、转化为友好响应;
  • 程序错误(Programmer):代码 bug——调了 undefined 的方法、类型错误。这类该让进程崩溃重启,靠监控发现后修代码。

两者处理策略不同,混为一谈是错误处理混乱的根源。

同步与异步错误的捕获

// 同步错误:Express 自动捕获
app.get('/sync', (req, res) => {
  throw new Error('同步错误');   // 自动进错误中间件
});

// 异步错误(Express 4):必须显式传递
app.get('/async', async (req, res, next) => {
  try {
    const data = await fetchData();
    res.json(data);
  } catch (err) {
    next(err);
  }
});

Express 4 的异步 handler 抛异常不会自动进错误中间件——这是最著名的坑。解法:包一层 asyncHandler、用 express-async-errors 补丁包,或直接升级 Express 5(自动捕获 Promise rejection)。

全局错误中间件与自定义错误类

class AppError extends Error {
  constructor(message, statusCode, isOperational = true) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = isOperational;
  }
}

class NotFoundError extends AppError {
  constructor(resource) {
    super(`${resource}不存在`, 404);
  }
}

// 放在所有路由之后
app.use((err, req, res, next) => {
  const status = err.statusCode || 500;
  const message = err.isOperational ? err.message : '服务器内部错误';

  logger.error({ err, url: req.url, method: req.method }, '请求处理失败');

  res.status(status).json({
    error: { message, ...(process.env.NODE_ENV !== 'production' && { stack: err.stack }) }
  });
});

关键规则:生产响应绝不带堆栈(泄露路径与依赖版本信息),非操作错误统一模糊化消息。

进程级兜底:uncaughtException 与 unhandledRejection

process.on('uncaughtException', (err) => {
  logger.fatal({ err }, '未捕获异常,进程即将退出');
  process.exit(1);   // 记录后退出,让进程管理器重启
});

process.on('unhandledRejection', (reason) => {
  logger.error({ err: reason }, '未处理的 Promise 拒绝');
  throw reason;      // 转为 uncaughtException 走统一流程
});

不要吞掉 uncaughtException 让进程继续跑——此时应用状态已不可信,苟活只会产生脏数据。记录日志、优雅关闭(停收新请求、结束进行中的)、退出重启,才是正解。

AbortController 与限流防护

慢请求取消:客户端断开后继续处理是浪费资源,用 AbortController 把取消信号传到数据库查询和下游调用。限流用 express-rate-limit 在入口处拦截过载——被打垮的服务对所有人都是 500,限流是对多数人的保护。

让错误中间件留下可追踪的现场

Express 错误中间件记录异常时,保留错误类别、路由模板、状态码和请求关联字段,避免仅输出一段没有上下文的 message。配置 DataKit 采集并解析日志后,可在观测云中按服务与版本检查错误趋势;再通过 Node.js APM 追踪已插桩的数据库和外部请求。被业务代码 catch 并正常返回的错误未必会自动标记为错误链路,需要按 SDK 的能力显式记录。

接入文档:日志采集 · Node.js APM 接入 · 日志与链路关联 · 监控器与告警策略。

常见问题(FAQ)

Q:错误中间件必须放最后吗?

是的。Express 按注册顺序匹配,错误中间件注册在路由之前会"接住"不到后面的错误。固定模式:路由 → 404 处理 → 错误中间件。

Q:next(err) 和 throw 有什么区别?

同步代码里等效(throw 会被 Express 捕获);异步回调/Promise 里 throw 只会变成未捕获异常——必须 next(err) 显式传递。升级 Express 5 后异步 throw/reject 也会被自动捕获。

Q:Joi/Zod 校验失败怎么转成统一错误响应?

在路由层 catch 校验错误,包装成 AppError('参数校验失败', 400) 并附带字段级错误明细后 next()——错误中间件按 statusCode 输出 400,前端拿到结构化字段错误。


参考文档

本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台