阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 错误处理中间件的特殊用法

错误处理中间件的特殊用法

作者:陈川 阅读数:35837人阅读 分类: Node.js

错误处理中间件的基本概念

Express中的错误处理中间件与其他中间件几乎相同,但有一个关键区别:它接受四个参数而不是三个。第四个参数err代表错误对象。基本结构如下:

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('出错了!');
});

这个中间件会捕获应用中抛出的所有错误。值得注意的是,错误处理中间件应该放在所有其他中间件和路由之后,这样才能确保它能捕获到所有可能发生的错误。

错误中间件的特殊参数顺序

错误处理中间件的参数顺序非常关键。Express通过参数数量来区分普通中间件和错误处理中间件:

// 普通中间件 - 三个参数
app.use((req, res, next) => {
  // 中间件逻辑
});

// 错误处理中间件 - 四个参数
app.use((err, req, res, next) => {
  // 错误处理逻辑
});

如果参数顺序错误,Express将无法正确识别它为错误处理中间件。例如,下面的代码不会作为错误处理中间件工作:

// 错误的参数顺序 - 不会被识别为错误处理中间件
app.use((req, err, res, next) => {
  // 这段代码永远不会执行
});

手动触发错误处理

可以通过next()函数手动将控制权传递给错误处理中间件:

app.get('/problematic-route', (req, res, next) => {
  try {
    // 可能抛出错误的代码
    throw new Error('故意抛出的错误');
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  }
});

这种方式特别适合在异步代码中处理错误,因为try-catch无法捕获异步操作中的错误:

app.get('/async-route', async (req, res, next) => {
  try {
    await someAsyncOperation();
  } catch (err) {
    next(err); // 必须手动传递错误
  }
});

错误中间件的链式调用

错误处理中间件可以像普通中间件一样链式调用,允许创建多个错误处理层:

// 第一个错误处理中间件 - 记录错误
app.use((err, req, res, next) => {
  console.error('错误记录:', err);
  next(err); // 传递给下一个错误处理中间件
});

// 第二个错误处理中间件 - 发送响应
app.use((err, req, res, next) => {
  res.status(500).json({
    error: err.message
  });
});

这种分层处理方式可以实现复杂的错误处理逻辑,比如先记录错误,然后根据错误类型返回不同的响应。

特定路由的错误处理

可以为特定路由创建专用的错误处理中间件:

const router = express.Router();

// 路由特定错误处理
router.use((err, req, res, next) => {
  if (err.type === 'RouteSpecificError') {
    res.status(400).send('路由特定错误处理');
  } else {
    next(err); // 传递给应用级错误处理
  }
});

router.get('/special', (req, res, next) => {
  const err = new Error('RouteSpecificError');
  err.type = 'RouteSpecificError';
  next(err);
});

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

这种方式允许在不同路由或路由组中实现定制化的错误处理逻辑。

错误分类处理

可以根据错误类型返回不同的HTTP状态码和响应:

app.use((err, req, res, next) => {
  if (err instanceof ValidationError) {
    return res.status(400).json({
      error: '验证错误',
      details: err.details
    });
  }
  
  if (err instanceof DatabaseError) {
    return res.status(503).json({
      error: '数据库错误',
      suggestion: '请稍后再试'
    });
  }
  
  // 未知错误
  res.status(500).json({
    error: '服务器内部错误'
  });
});

这种模式使得API能够根据不同的错误类型提供更有意义的响应。

异步错误处理技巧

在异步函数中,错误处理需要特别注意。Express 5将原生支持异步函数中的错误冒泡,但在Express 4中需要手动处理:

// Express 4中的处理方式
app.get('/async', (req, res, next) => {
  someAsyncFunction()
    .then(result => res.json(result))
    .catch(next); // 将Promise rejection传递给错误处理中间件
});

// 或者使用async/await
app.get('/async-await', async (req, res, next) => {
  try {
    const result = await someAsyncFunction();
    res.json(result);
  } catch (err) {
    next(err);
  }
});

错误中间件与Promise结合

可以创建一个包装函数来自动捕获Promise rejection:

function asyncHandler(fn) {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next))
      .catch(next);
  };
}

app.get('/wrapped', asyncHandler(async (req, res) => {
  const data = await fetchData();
  if (!data.valid) {
    throw new Error('无效数据'); // 会自动被asyncHandler捕获
  }
  res.json(data);
}));

这种模式减少了样板代码,使异步路由更加简洁。

错误中间件的调试技巧

在开发环境中,可能需要更详细的错误信息:

app.use((err, req, res, next) => {
  const isDevelopment = process.env.NODE_ENV === 'development';
  
  res.status(err.status || 500).json({
    message: err.message,
    stack: isDevelopment ? err.stack : undefined,
    ...(isDevelopment && { originalError: err })
  });
});

这样可以只在开发环境中暴露堆栈跟踪等敏感信息。

错误中间件的性能考虑

错误处理中间件中应避免执行耗时操作,否则可能影响应用的错误恢复能力:

// 不推荐 - 同步写入日志可能阻塞事件循环
app.use((err, req, res, next) => {
  fs.writeFileSync('error.log', err.stack);
  next(err);
});

// 推荐 - 使用异步日志记录
app.use(async (err, req, res, next) => {
  await logErrorAsync(err);
  next(err);
});

自定义错误类增强处理

创建自定义错误类可以使错误处理更加结构化:

class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
    Error.captureStackTrace(this, this.constructor);
  }
}

// 使用自定义错误
app.get('/custom-error', (req, res, next) => {
  next(new AppError('资源未找到', 404));
});

// 处理自定义错误
app.use((err, req, res, next) => {
  if (err instanceof AppError) {
    return res.status(err.statusCode).json({
      status: 'error',
      message: err.message
    });
  }
  next(err);
});

错误中间件与第三方日志服务集成

可以将错误发送到外部日志服务如Sentry或Loggly:

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

app.use((err, req, res, next) => {
  Sentry.captureException(err);
  
  // 仍然需要向客户端返回响应
  res.status(500).json({
    error: '内部服务器错误',
    referenceId: Sentry.lastEventId()
  });
});

404错误的特殊处理

虽然404不是技术错误,但可以通过错误处理中间件统一处理:

// 捕获404并转发到错误处理程序
app.use((req, res, next) => {
  next(new AppError('未找到', 404));
});

// 然后在错误处理中间件中统一处理
app.use((err, req, res, next) => {
  if (err.statusCode === 404) {
    return res.status(404).render('404');
  }
  next(err);
});

错误中间件的测试策略

测试错误处理中间件需要模拟错误场景:

const request = require('supertest');
const app = require('../app');

describe('错误处理中间件', () => {
  it('应该处理500错误', async () => {
    // 模拟抛出错误的测试路由
    app.get('/test-error', (req, res, next) => {
      next(new Error('测试错误'));
    });
    
    const res = await request(app).get('/test-error');
    expect(res.status).toBe(500);
    expect(res.body.error).toBeDefined();
  });
});

错误中间件与API文档结合

可以使用错误处理中间件自动生成API错误文档:

const apiDocs = {
  '/api/users': {
    errors: [
      { code: 400, description: '无效输入' },
      { code: 500, description: '服务器错误' }
    ]
  }
};

app.use((err, req, res, next) => {
  const routeDocs = apiDocs[req.path];
  if (routeDocs) {
    err.documentation = routeDocs.errors.find(e => e.code === err.statusCode);
  }
  next(err);
});

错误中间件的安全考虑

在错误响应中应避免泄露敏感信息:

app.use((err, req, res, next) => {
  // 清理错误对象,移除敏感信息
  const safeError = {
    message: err.message,
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  };
  
  res.status(err.status || 500).json({
    error: safeError
  });
});

错误中间件的多语言支持

可以根据请求头返回本地化的错误消息:

const errorMessages = {
  en: {
    database_error: 'Database error'
  },
  zh: {
    database_error: '数据库错误'
  }
};

app.use((err, req, res, next) => {
  const lang = req.acceptsLanguages('en', 'zh') || 'en';
  const message = errorMessages[lang][err.code] || err.message;
  
  res.status(err.status || 500).json({
    error: message
  });
});

错误中间件与前端框架协作

可以返回结构化错误供前端框架处理:

app.use((err, req, res, next) => {
  res.status(err.status || 500).json({
    error: {
      message: err.message,
      code: err.code,
      fields: err.fields // 针对表单验证错误
    }
  });
});

这样前端可以统一处理API返回的错误结构。

错误中间件的监控集成

将错误与监控系统集成:

const statsd = require('node-statsd')();

app.use((err, req, res, next) => {
  // 根据错误类型递增计数器
  statsd.increment(`errors.${err.constructor.name}`);
  
  next(err);
});

错误中间件的请求重试逻辑

对于临时性错误,可以返回重试建议:

app.use((err, req, res, next) => {
  if (err.isRetryable) {
    res.set('Retry-After', '10');
    return res.status(503).json({
      error: '服务暂时不可用',
      retryAfter: 10
    });
  }
  next(err);
});

错误中间件与断路器模式

实现简单的断路器模式:

const circuitBreaker = {
  isOpen: false,
  lastFailure: null
};

app.use((err, req, res, next) => {
  if (err.isCircuitBreaker) {
    circuitBreaker.isOpen = true;
    circuitBreaker.lastFailure = Date.now();
  }
  
  if (circuitBreaker.isOpen) {
    return res.status(503).json({
      error: '服务暂时不可用',
      willRetryAt: new Date(circuitBreaker.lastFailure + 60000)
    });
  }
  
  next(err);
});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌