Published on

Express 处理同步/异步错误 | asyncHandler | errorHandler

Authors
  • avatar
    Name
    Shelton Ma
    Twitter

处理同步和异步错误

在 Express 中,你可以在 controller 层抛出异常,而不需要在每个 server 层使用 try-catch 来捕获异常.最佳实践是通过全局错误处理中间件来统一处理异常.这样可以更简洁地管理错误逻辑,避免冗余的 try-catch 代码

实现思路: Controller 层抛出异常, 使用 asyncHandler 中间件封装异步路由

1. 错误处理中间件

// src/middlewares/error.middleware.ts
import { NextFunction, Request, Response } from "express";

export const errorHandler = (
  err: Error & { statusCode?: number },
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const statusCode = err.statusCode ?? 500;

  // 响应内容,避免暴露敏感信息
  const errorMessage =
    process.env.NODE_ENV === "production"
      ? "Internal Server Error"
      : err.message || "Unknown error";

  console.error({
    message: err.message || "Unknown error",
    stack: err.stack,
  });

  res.status(statusCode).json({
    success: false,
    message: errorMessage,
  });
};

2. 异步/同步错误捕获器

// src/utils/async-handler.ts
import { NextFunction, Request, Response } from "express";

/**
 * @description 异步/同步错误捕获器
 * @param fn 控制器/服务函数
 */
export const asyncHandler = (
  fn: (req: Request, res: Response, next: NextFunction) => Promise<void> | void
) => {
  return (req: Request, res: Response, next: NextFunction) => {
    try {
      const result = fn(req, res, next);

      // 检查是否为 Promise,处理异步异常
      if (result && result instanceof Promise) {
        result.catch((error) => {
          console.error(`[asyncHandler Error] ${error.message}`);
          next(error); // 将异常传递给全局错误处理中间件
        });
      }
    } catch (error) {
      console.error(`[asyncHandler Error] ${error.message}`);
      next(error); // 处理同步异常
    }
  };
};

3. Controller直接抛出异常, 在Router中无需 try-catch

// /src/controllers/user.controller.ts
const error = new Error("User not found");
(error as any).statusCode = 404;
throw error;

// /src/routes/user.route.ts
import express from "express";
import { asyncHandler } from "../utils/async-handler";
import { getUser } from "../controllers/user-controller";

const router = express.Router();

router.get("/user/:id", asyncHandler(getUser)); // 不需要 try-catch

export default router;