示例说明
Express 4 的路由回调里抛出的 Promise 拒绝不会自动进入错误中间件,未捕获会悬挂请求直到超时。wrap 把 handler 的执行包进 Promise.resolve 的 catch,把异步错误转交 next,让全部错误汇聚到四参数错误中间件统一出口,这比每个路由各写 try-catch 干净得多。
技术要点
- wrap 统一捕获路由异步拒绝
- HttpError 携带状态码与公开细节
- 5xx 记堆栈但响应不泄漏内部信息
代码解读
HttpError 区分业务可展示错误与内部错误:404 时消息与 detail 可以给客户端,500 时只回笼统提示而把堆栈写进服务端日志。findOrder 模拟数据层,missing 时返回 null 触发 404 分支。
使用建议
错误中间件必须放在所有路由之后且保持四参数签名,否则不被识别。对外错误文案不要包含 SQL 或文件路径。结合请求 ID 把日志与响应头关联,方便用户报障后检索。
代码示例
import express from 'express';
class HttpError extends Error {
constructor(status, message, public detail = null) {
super(message);
this.status = status;
this.detail = detail;
}
}
const wrap = (handler) => (req, res, next) => {
Promise.resolve(handler(req, res, next)).catch(next);
};
const app = express();
app.use(express.json());
app.get('/orders/:id', wrap(async (req, res) => {
const order = await findOrder(req.params.id);
if (!order) throw new HttpError(404, 'order not found');
res.json(order);
}));
app.use((err, req, res, next) => {
const status = err instanceof HttpError ? err.status : 500;
if (status >= 500) console.error('unhandled:', err.stack);
res.status(status).json({
error: err.message || 'internal error',
detail: status < 500 ? err.detail : undefined,
});
});
async function findOrder(id) {
if (id === 'missing') return null;
return { id, total: 42 };
}
app.listen(3000);