您现在的位置是:网站首页 > 错误处理中间件的特殊用法文章详情
错误处理中间件的特殊用法
陈川
【
Node.js
】
41747人已围观
8119字
错误处理中间件的基本概念
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('专属错误处理');
});
多层级错误处理策略
大型应用可能需要分层的错误处理架构:
- 路由级错误处理:处理特定路由的已知错误类型
- 控制器级错误处理:处理业务逻辑中的领域错误
- 全局错误处理:兜底处理所有未捕获异常
// 业务逻辑层错误分类
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); // 其余的走常规流程
});
// 收集两种处理方式的指标进行对比
上一篇: <thead>-表头部分
下一篇: 异步中间件的实现方式