You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Express无法处理控制器或其他类中抛出的错误

Express异步控制器抛错无法触发错误中间件的解决方案

在Express中,路由处理函数同步抛出错误时能正常触发错误中间件,但控制器中的异步方法抛错时,程序直接崩溃,错误中间件不生效。

问题复现

同步路由抛错可正常触发错误中间件:

# routes.ts
// 同步抛错,能触发错误中间件
router.get('/', (req, res) => {
    throw new BadRequestError("teste")
})

但异步控制器方法抛错时失效:

# UserController.ts
export class UserController {
    static async registerNewUser(req: Request, res: Response) {
        const newUser = new User(req.body)
        const alreadyExists = await User.findUser(newUser)
        if (alreadyExists) {
            throw new BadRequestError("teste")
        }
    }
}

# routes.ts
router.post('/register', UserController.registerNewUser)

当前项目配置:

# app.ts
const app = express()
app.use(express.json());
app.use(router)
app.use(errorMiddleware)
export { app }

# server.ts
import { app } from "./app";
const PORT = process.env.PORT
app.listen(PORT, () => console.log(`Server is running on port ${PORT}`))

原因

Express 默认只能捕获同步代码中抛出的错误。异步函数(含async/await)的错误会进入Promise的拒绝状态,无法被Express自动捕获,导致程序崩溃而不触发错误中间件。


解决方案

方案1:手动捕获错误并传递给next

在异步控制器方法中添加try/catch,捕获错误后调用next(err)将错误传递给错误中间件(需给方法添加NextFunction参数):

# UserController.ts
import { Request, Response, NextFunction } from 'express';
import { BadRequestError } from '../errors/BadRequestError';

export class UserController {
    static async registerNewUser(req: Request, res: Response, next: NextFunction) {
        try {
            const newUser = new User(req.body)
            const alreadyExists = await User.findUser(newUser)
            
            if (alreadyExists) {
                throw new BadRequestError("teste")
            }
            
            // 其他业务逻辑:保存用户、返回响应
            res.status(201).json({ message: 'User created' })
        } catch (err) {
            next(err); // 将错误传递给错误中间件
        }
    }
}

方案2:使用通用异步包装器(推荐)

编写一个通用包装函数,自动捕获异步路由处理函数的错误并传递给next,避免重复编写try/catch:

# utils/asyncHandler.ts
import { Request, Response, NextFunction } from 'express';

// 包装异步函数,自动捕获错误并传给next
export const asyncHandler = (fn: (req: Request, res: Response, next: NextFunction) => Promise<any>) => {
    return (req: Request, res: Response, next: NextFunction) => {
        fn(req, res, next).catch(next);
    };
};

在路由中使用该包装器:

# routes.ts
import { asyncHandler } from './utils/asyncHandler';
import { UserController } from './controllers/UserController';

router.post('/register', asyncHandler(UserController.registerNewUser));

方案3:确保错误中间件格式正确

Express的错误中间件必须包含4个参数(err, req, res, next),否则会被当作普通中间件处理:

# middleware/errorMiddleware.ts
import { Request, Response, NextFunction } from 'express';

export const errorMiddleware = (err: any, req: Request, res: Response, next: NextFunction) => {
    // 打印错误栈用于调试
    console.error('Error:', err.stack);
    
    // 自定义错误类需包含statusCode,否则默认返回500状态码
    const statusCode = err.statusCode || 500;
    res.status(statusCode).json({
        message: err.message || 'Internal Server Error',
        // 开发环境可返回错误栈,生产环境建议关闭
        // stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
    });
};

同时需确保自定义错误类正确设置原型链(避免instanceof判断失效):

# errors/BadRequestError.ts
export class BadRequestError extends Error {
    statusCode = 400;
    
    constructor(message: string) {
        super(message);
        // 修复原型链,确保类型判断正常
        Object.setPrototypeOf(this, BadRequestError.prototype);
    }
}

内容的提问来源于stack exchange,提问作者SiqMello

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 10:30:49