错误处理中间件的特殊用法
错误处理中间件的基本概念
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
上一篇:中间件的执行顺序与控制
下一篇:异步中间件的实现方式