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,前端拿到结构化字段错误。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。