Express 错误处理

Express 应用健壮的错误处理:自定义错误类、异步包装器、集中错误中间件和环境感知响应。

1. 自定义错误类

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

class NotFoundError extends AppError {
  constructor(resource = '资源') {
    super(`${resource}不存在`, 404, 'NOT_FOUND');
  }
}

class ValidationError extends AppError {
  constructor(message, fields = {}) {
    super(message, 422, 'VALIDATION_ERROR');
    this.fields = fields;
  }
}

module.exports = { AppError, NotFoundError, ValidationError };

2. 异步错误处理

// 工具包装器 — 路由中无需 try/catch
const asyncHandler = fn => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

// 使用
router.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await db.users.findById(req.params.id);
  if (!user) throw new NotFoundError('用户');
  res.json(user);
}));

3. 集中错误中间件

function errorHandler(err, req, res, next) {
  if (err.name === 'ValidationError') {
    const fields = Object.fromEntries(
      Object.entries(err.errors).map(([k, v]) => [k, v.message])
    );
    return res.status(422).json({ error: '验证失败', fields });
  }

  if (err.name === 'JsonWebTokenError') {
    return res.status(401).json({ error: 'token 无效' });
  }

  if (err.isOperational) {
    const body = { error: err.message, code: err.code };
    if (err.fields) body.fields = err.fields;
    return res.status(err.statusCode).json(body);
  }

  console.error('未处理错误:', err);
  const message = process.env.NODE_ENV === 'production'
    ? '服务器内部错误' : err.message;
  res.status(500).json({ error: message });
}

// 必须最后注册
app.use(errorHandler);

4. 404 处理器

// 放在所有路由之后、errorHandler 之前
app.use((req, res, next) => {
  next(new NotFoundError(`路由 ${req.method} ${req.path}`));
});

5. 未处理的拒绝与异常

process.on('unhandledRejection', (reason) => {
  console.error('未处理的 Promise 拒绝:', reason);
  server.close(() => process.exit(1));
});

process.on('uncaughtException', (err) => {
  console.error('未捕获的异常:', err);
  process.exit(1);
});

process.on('SIGTERM', () => {
  server.close(() => process.exit(0));
});

6. HTTP 状态码参考

代码名称使用场景
200OK成功的 GET/PUT/PATCH
201Created成功创建资源
204No Content成功删除
400Bad Request请求格式错误
401Unauthorized需要身份验证
403Forbidden已认证但无权限
404Not Found资源不存在
422Unprocessable Entity验证错误
429Too Many Requests超出限流
500Internal Server Error服务器意外错误