Skip to content

Express 异步错误自动捕获方案(wrapRouterStack)

在 Express 中,异步路由中的 throw new Error() 不会自动进入全局错误处理器,这是因为 Express 默认只捕获同步错误。

例如:

js
router.get('/demo', async (req, res) => {
  throw new Error("Boom")
})

这段代码不会触发全局错误处理中间件!

为了解决这个问题,我们可以使用 wrapRouterStack 自动为所有路由 handler 添加错误捕获包装,使同步和异步的错误都能正确传递给 next(err)

✨ wrapRouterStack — 自动包装所有路由的错误捕获

ts
/**
 * 为指定 Router 实例中的所有路由处理函数添加异步错误捕获包装。
 *
 * Express 默认不会自动捕获 async/await 中的异常,
 * 如果异步路由中 `throw new Error()`,应用不会自动进入全局错误处理中间件。
 * 
 * 本函数通过遍历 Router 内部的 `stack`,
 * 对每个 route 中的 handler 函数进行封装:
 * - 对同步错误使用 try/catch 捕获;
 * - 对返回 Promise 的异步函数添加 `.catch(next)`;
 * 
 * 从而确保任何同步或异步异常都能被正确传递给全局错误处理器。
 *
 * @param {Router} router - 需要进行错误捕获包装的 Express Router 实例。
 *
 * @example
 * const router = Router()
 * router.get('/demo', async (req, res) => {
 *   throw new Error('This will be caught!')
 * })
 * wrapRouterStack(router)
 */
function wrapRouterStack(router: Router): void {
    router.stack.forEach(layer => {
        if (!layer.route) return
        for (const stack of layer.route.stack) {
            const handler = stack.handle
            if (typeof handler === 'function') {
                stack.handle = function (req: Request, res: Response, next: NextFunction) {
                    try {
                        const ret = handler(req, res, next)
                        // 如果返回 Promise,则捕获异步异常
                        if (ret && typeof ret.then === 'function') {
                            ret.catch(next)
                        }
                    } catch (err) {
                        // 捕获同步异常
                        next(err)
                    }
                }
            }
        }
    })
}

✨ 全局错误处理中间件(与 wrapRouterStack 搭配)

ts
import type { Request, Response, NextFunction } from 'express'

module.exports = (error: any, req: Request, resp: Response, next: NextFunction) => {

    // 如果响应头已经发出,交给 Express 默认处理
    if (resp.headersSent) {
        return next(error)
    }

    // 如果有自定义的失败处理函数,则调用
    if (resp.fail) {
        return resp.fail(error)
    }

    const status = error.status || error.statusCode || 500

    resp.status(status).json({
        success: false,
        message: error.message || '服务器内部错误',
        stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
    })
}

✔ 使用方式示例

ts
const router = Router()

router.get('/test', async (req, res) => {
  throw new Error("Test error")
})

wrapRouterStack(router)
app.use('/api', router)

无论是:

  • 同步错误
  • 异步错误
  • Promise.reject()
  • throw new Error

都能正确进入全局错误处理中间件。

🎯 总结

问题解决方式
Express 默认无法捕获 async/await 中的错误用 wrapRouterStack 自动为所有路由补充 .catch(next)
无法捕获同步错误wrapRouterStack 内部的 try/catch
需要统一的错误返回结构全局错误处理中间件

这是 Express 最优雅、可扩展、零侵入的错误处理方案。