如何在 Express 中正确包装异步请求处理器以统一捕获错误

如何在 Express 中正确包装异步请求处理器以统一捕获错误

本文介绍如何通过高阶函数 trycatch 安全包装 express 异步路由处理器,解决 async/await 中未被捕获的 promise 拒绝导致进程崩溃的问题,并确保所有异步错误都能被全局错误处理中间件接管。

你遇到的问题非常典型:当前的 tryCatch 包装器虽然能同步调用 func(req, res, next),但它无法等待异步函数的执行结果。当传入的是 async (req, res, next) => { … } 时,该函数会立即返回一个 Promise;而你的 try 块中只是调用了这个函数,并未 await 其返回的 Promise —— 因此内部 throw 或 Promise rejection(如 MongoDB 的 E11000 duplicate key 错误)不会触发 catch,而是成为「未处理的 Promise rejection」,最终导致 Node.js 进程崩溃或静默失败。

✅ 正确做法是:在包装器中 await 异步处理器的执行,并统一将同步异常与 Promise rejection 转发给 next():

// utils/tryCatch.ts
import { RequestHandler } from 'express';

export const tryCatch = (handler: RequestHandler): RequestHandler => {
  return async (req, res, next) => {
    try {
      // ✅ 关键:await 确保 Promise rejection 被捕获
      await handler(req, res, next);
    } catch (err) {
      // ⚠️ 注意:若 handler 内部已调用 next(err),此处可能重复触发
      // 推荐 handler 内不手动调用 next(err),统一由 tryCatch 处理
      next(err);
    }
  };
};

使用方式如下(简洁、无冗余 try-catch):

造次

造次

Liblib打造的AI原创IP视频创作社区

下载

// controllers/auth.ts
import { tryCatch } from '../utils/tryCatch';

export const login = tryCatch(async (req, res) => {
  const { email, password } = req.body;
  const user = await User.findOne({ email });
  if (!user || !(await bcrypt.compare(password, user.password))) {
    throw new Error('Invalid credentials'); // ✅ 自动被 tryCatch 捕获
  }
  res.json({ token: signToken(user) });
});

? 重要注意事项

  • ❌ 不要在被包装的异步函数内调用 next(err) —— 否则 tryCatch 可能因 res 已发送而报错(如 Cannot set headers after they are sent);
  • ✅ 必须在 Express 应用中注册全局错误处理中间件(4 参数签名),否则 next(err) 将无处可去:
// app.ts
app.use('/api/auth', authRouter);

// ✅ 全局错误处理器(必须放在所有路由之后)
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error('Unhandled error:', err);
  res.status(500).json({ 
    success: false, 
    message: 'Something went wrong', 
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) 
  });
});

// ✅ 可选:处理未捕获的 Promise rejection(兜底防护)
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  process.exit(1); // 或优雅降级
});

? 总结:tryCatch 的本质是「Promise-aware 错误拦截器」。它不是替代 Express 错误处理机制,而是与其协同工作——将异步错误标准化为 next(err) 调用,交由 Express 的错误传播链统一调度。这一模式显著提升代码可维护性,是构建健壮 Express API 的推荐实践。

https://www.php.cn/faq/2000522.html

发表回复

Your email address will not be published. Required fields are marked *