您现在的位置是:网站首页 > 错误处理中间件的特殊用法文章详情

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

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

Express框架中的错误处理中间件与其他中间件的关键区别在于参数数量。错误处理中间件接收四个参数:err, req, res, next。当调用next(err)时,Express会跳过所有常规中间件,直接进入错误处理流程。

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

错误中间件的特殊执行顺序

错误处理中间件的位置至关重要。它必须放在所有路由和其他中间件之后,才能捕获到前面抛出的错误。但通过巧妙安排,可以实现更精细的错误控制:

// 特定路由的错误处理
app.get('/dangerous', (req, res, next) => {
  riskyOperation()
    .then(result => res.send(result))
    .catch(next); // 跳转到专属错误处理器
}, (err, req, res, next) => {
  res.status(418).send('专属错误处理');
});

多层级错误处理策略

大型应用可能需要分层的错误处理架构:

  1. 路由级错误处理:处理特定路由的已知错误类型
  2. 控制器级错误处理:处理业务逻辑中的领域错误
  3. 全局错误处理:兜底处理所有未捕获异常
// 业务逻辑层错误分类
class ValidationError extends Error {}
class DatabaseError extends Error {}

// 分层处理示例
app.post('/api/data', 
  validateInput, // 可能抛出ValidationError
  processData,   // 可能抛出DatabaseError
  (err, req, res, next) => {
    if (err instanceof ValidationError) {
      return res.status(400).json({ error: err.message });
    }
    next(err); // 其他错误传递给下一层
  },
  (err, req, res, next) => {
    // 全局错误日志记录
    logger.error(err); 
    res.status(500).end();
  }
);

异步错误的特殊处理

在async/await场景中,错误处理需要额外包装:

const asyncHandler = fn => (req, res, next) => 
  Promise.resolve(fn(req, res, next)).catch(next);

app.get('/async', asyncHandler(async (req, res) => {
  const data = await fetchData();
  if (!data) throw new Error('Data not found');
  res.json(data);
}));

错误转换与标准化

将各种错误转换为统一格式的响应:

app.use((err, req, res, next) => {
  const standardizedError = {
    code: err.code || 500,
    message: err.message || 'Internal Server Error',
    details: process.env.NODE_ENV === 'development' ? err.stack : undefined
  };
  res.status(standardizedError.code).json(standardizedError);
});

中间件链中的错误中断

利用错误处理中间件实现条件流程控制:

app.use((req, res, next) => {
  if (!req.headers['x-auth']) {
    const err = new Error('Unauthorized');
    err.status = 401;
    return next(err); // 直接跳转到错误处理
  }
  next();
});

// 常规中间件不会被执行如果上面触发了错误
app.use((req, res) => {
  res.send('Protected content');
});

错误中间件的测试技巧

测试错误处理中间件的特殊方法:

// 测试工具函数
const testErrorMiddleware = (middleware, error) => {
  const req = mockRequest();
  const res = mockResponse();
  const next = jest.fn();
  
  middleware(error, req, res, next);
  return { req, res, next };
};

// 测试用例
it('should handle 404 errors', () => {
  const { res } = testErrorMiddleware(notFoundHandler, {
    status: 404,
    message: 'Not Found'
  });
  expect(res.status).toHaveBeenCalledWith(404);
});

性能监控与错误处理结合

将错误处理与性能追踪集成:

app.use((err, req, res, next) => {
  const start = process.hrtime();
  
  res.on('finish', () => {
    const duration = process.hrtime(start);
    metrics.trackError({
      error: err.name,
      duration: duration[0] * 1e3 + duration[1] / 1e6,
      path: req.path
    });
  });

  next(err); // 继续正常错误处理流程
});

自定义错误类的进阶用法

创建丰富的错误类型体系:

class HttpError extends Error {
  constructor(status, message) {
    super(message);
    this.status = status;
    this.expose = true; // 标记为客户端安全错误
  }
}

app.use((err, req, res, next) => {
  if (err.expose) {
    return res.status(err.status).send(err.message);
  }
  // 否则处理为500错误
  res.status(500).send('Internal Server Error');
});

错误中间件的调试技巧

开发环境下的增强调试支持:

app.use((err, req, res, next) => {
  if (process.env.NODE_ENV === 'development') {
    require('express-error-preview')(err, req, res);
    return;
  }
  next(err);
});

错误处理的AOP模式实现

面向切面的错误处理方式:

function errorAspect(handler) {
  return async (req, res, next) => {
    try {
      await handler(req, res, next);
    } catch (err) {
      // 前置处理
      err.timestamp = new Date();
      err.requestId = req.id;
      
      // 传递给错误中间件
      next(err);
      
      // 后置处理
      console.log(`Error handled at ${err.timestamp}`);
    }
  };
}

app.get('/protected', errorAspect(async (req, res) => {
  // 业务逻辑
}));

错误中间件的状态管理

在错误处理过程中维护请求状态:

app.use((req, res, next) => {
  req.state = { start: Date.now() };
  next();
});

app.use((err, req, res, next) => {
  req.state.error = err.name;
  req.state.duration = Date.now() - req.state.start;
  next(err);
});

app.use((err, req, res, next) => {
  // 可以访问之前中间件设置的状态
  console.log(`请求耗时 ${req.state.duration}ms`);
  res.status(500).json(req.state);
});

错误中间件的插件化架构

可插拔的错误处理模块设计:

// error-plugins.js
const plugins = [
  require('./logging-plugin'),
  require('./notification-plugin'),
  require('./formatting-plugin')
];

module.exports = (err, req, res, next) => {
  plugins.forEach(plugin => plugin.execute(err, req, res));
  next(err);
};

// app.js
app.use(require('./error-plugins'));

错误中间件的条件分支

根据请求特征选择不同错误处理策略:

app.use((err, req, res, next) => {
  if (req.accepts('html')) {
    // 返回HTML错误页面
    res.render('error', { error: err });
  } else if (req.accepts('json')) {
    // 返回JSON错误响应
    res.json({ error: err.message });
  } else {
    // 默认纯文本响应
    res.type('txt').send(err.message);
  }
});

错误中间件的重试机制

实现自动错误恢复尝试:

const withRetry = (middleware, options = {}) => {
  const { maxRetries = 3 } = options;
  let retryCount = 0;
  
  return function wrappedMiddleware(err, req, res, next) {
    if (retryCount < maxRetries) {
      retryCount++;
      return middleware(req, res, () => next(err));
    }
    next(err);
  };
};

app.use(withRetry(flakyServiceMiddleware));

错误中间件的请求重定向

将错误转换为重定向响应:

app.use((err, req, res, next) => {
  if (err.redirectUrl) {
    return res.redirect(err.redirectUrl);
  }
  next(err);
});

// 使用示例
app.post('/checkout', (req, res, next) => {
  if (!req.user) {
    const err = new Error('Login required');
    err.redirectUrl = '/login?returnTo=/checkout';
    return next(err);
  }
  // 正常处理逻辑
});

错误中间件的流处理支持

处理流操作中的错误:

app.get('/video', (req, res, next) => {
  const stream = fs.createReadStream('./video.mp4')
    .on('error', err => next(err)) // 将流错误传递给错误中间件
    .pipe(res);
});

// 专门的流错误处理
app.use((err, req, res, next) => {
  if (err.code === 'ENOENT') {
    res.status(404).send('File not found');
  } else if (req.accepts('video')) {
    // 返回替代视频流
    fs.createReadStream('./default.mp4').pipe(res);
  } else {
    next(err);
  }
});

错误中间件的内存管理

防止内存泄漏的实践:

app.use((err, req, res, next) => {
  // 清理请求相关的临时资源
  if (req.tempFiles) {
    req.tempFiles.forEach(file => fs.unlinkSync(file));
  }
  
  // 确保错误对象不会保留过大内存
  if (err.largePayload) {
    err.largePayload = null;
  }
  
  next(err);
});

错误中间件的请求修改

在错误处理过程中修改请求对象:

app.use((err, req, res, next) => {
  if (err.status === 401) {
    // 添加认证挑战头
    res.set('WWW-Authenticate', 'Bearer');
    // 修改请求标记
    req.failedAuthAttempt = true;
  }
  next(err);
});

// 后续中间件可以基于修改后的请求做决策
app.use((err, req, res, next) => {
  if (req.failedAuthAttempt) {
    securitySystem.logAttempt(req.ip);
  }
  next(err);
});

错误中间件的响应拦截

修改错误响应前的最后机会:

app.use((err, req, res, next) => {
  const originalSend = res.send;
  res.send = function(body) {
    // 拦截所有错误响应
    if (res.statusCode >= 400) {
      body = `ERROR: ${body}`;
    }
    originalSend.call(res, body);
  };
  next(err);
});

错误中间件的依赖注入

通过闭包实现配置化错误处理:

const createErrorHandler = (options = {}) => {
  return (err, req, res, next) => {
    if (options.logErrors) {
      console.error(`[${new Date().toISOString()}]`, err);
    }
    
    const message = options.verbose 
      ? err.stack 
      : 'An error occurred';
      
    res.status(err.status || 500).send(message);
  };
};

// 使用不同配置
app.use(createErrorHandler({ logErrors: true, verbose: false }));

错误中间件的A/B测试

实验性错误处理策略:

app.use((err, req, res, next) => {
  // 对50%的请求使用新错误处理
  if (Math.random() > 0.5) {
    return experimentalErrorHandler(err, req, res, next);
  }
  next(err); // 其余的走常规流程
});

// 收集两种处理方式的指标进行对比

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步