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

Express 4自定义错误处理中间件未触发问题排查

Express 4自定义错误处理中间件不生效问题

我在使用Express 4时遇到自定义错误处理中间件无法触发的问题:

  • 已通过express-async-errors处理异步错误,但同步/异步抛出的错误都无法触发全局错误处理中间件
  • 只有将错误处理中间件的app.use()放在单个路由文件末尾时才生效,无法作用于所有路由
  • 控制台能捕获到异常,但全局注册的错误中间件代码完全不执行(比如添加console.log无输出)

相关代码

路由文件(单独文件)

module.exports.set = (app, apiVersion) => {
  app.get(
    `${apiVersion}/User/iscool`, UserTest.TheUserTestWorks,
  );

  app.get(
    `${apiVersion}/User/error`, UserTest.TheUserTestThrowAnErrorSynchronously,
  );

  app.get(
    `${apiVersion}/User/AsyncError`, UserTest.TheUserTestThrowAnErrorAsync,
  );
};

index.js中加载路由的代码

fs.readdir("./routers", (err, routes) => {
  routes.map((route) => {
    const module = require(`./routers/${route.split(".").slice(0, -1).join(".")}`);
    module.set(app, apiVersion);
    return route;
  });
  return true;
});

错误处理中间件

const Sentry = require("@sentry/node");

function genericExceptionHandlerMiddleware(err, req, res, next) {
  Sentry.captureException(err);
  res.status(500).send("An unknown error occurred");
  next(err);
}

module.exports = genericExceptionHandlerMiddleware;

index.js中注册中间件的位置

// 在index.js末尾、module.exports = app之前添加
app.use(genericExceptionHandlerMiddleware);

问题根源

fs.readdir是异步方法,导致路由注册的时机晚于错误处理中间件:

  1. 先执行fs.readdir,触发异步文件读取(回调会在事件循环下一轮执行)
  2. 同步执行app.use(genericExceptionHandlerMiddleware),错误处理中间件被提前注册到中间件栈
  3. 事件循环到回调时才开始注册所有路由

Express的中间件/路由是按注册顺序匹配的,错误处理中间件必须放在所有路由之后才能捕获路由抛出的错误。如果错误中间件在路由之前注册,当路由抛出错误时,next(err)会从路由之后的中间件开始查找,此时找不到已注册的错误处理中间件,自然无法触发。

解决方案

方案1:改用同步方式加载路由

将fs.readdir替换为同步方法fs.readdirSync,确保路由全部注册完成后再添加错误处理中间件:

const fs = require('fs');

// 同步读取路由目录,确保路由先注册
const routes = fs.readdirSync("./routers");
routes.map((route) => {
  const module = require(`./routers/${route.split(".").slice(0, -1).join(".")}`);
  module.set(app, apiVersion);
  return route;
});

// 路由注册完成后,再添加全局错误处理中间件
app.use(genericExceptionHandlerMiddleware);

方案2:用异步Promise确保路由加载完成后注册中间件

如果不想用同步方法,可通过Promise包装异步操作,等待所有路由加载完成后再注册错误中间件:

const fs = require('fs').promises;

async function loadRoutes() {
  const routes = await fs.readdir("./routers");
  await Promise.all(routes.map((route) => {
    const module = require(`./routers/${route.split(".").slice(0, -1).join(".")}`);
    module.set(app, apiVersion);
  }));
  // 路由全部加载完成后注册错误处理中间件
  app.use(genericExceptionHandlerMiddleware);
}

loadRoutes().catch(err => console.error('路由加载失败:', err));

额外优化

错误处理中间件中无需调用next(err),因为已经发送了响应,继续调用会触发Express默认的错误处理逻辑(可能导致重复响应),可修改为:

function genericExceptionHandlerMiddleware(err, req, res, next) {
  Sentry.captureException(err);
  res.status(500).send("An unknown error occurred");
  // 移除next(err)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 06:20:24