← 中文资源

简体中文版本

Express 异步错误处理包装器

用包装器把路由里的异步异常送进 Express 错误中间件:按错误类型分类状态码,服务端记录堆栈,对外隐藏内部细节。

阅读英文原文

示例说明

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);