错误处理中间件的编写技巧
错误处理中间件的基本概念
错误处理中间件是Koa2应用中捕获和处理错误的中心位置。它通常作为第一个中间件注册,确保能捕获后续中间件抛出的任何错误。在Koa2中,错误处理中间件通过try/catch块包裹yield next来实现。
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
ctx.status = err.status || 500
ctx.body = err.message
ctx.app.emit('error', err, ctx)
}
})
错误分类与处理策略
Koa2应用中错误大致可分为三类:操作错误、编程错误和第三方错误。操作错误如无效的用户输入,应该返回4xx状态码;编程错误如未定义变量,应该返回500状态码;第三方错误如数据库连接失败,需要根据具体情况处理。
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
// 操作错误
if (err.isOperational) {
ctx.status = err.statusCode || 400
ctx.body = { error: err.message }
}
// 编程错误
else {
ctx.status = 500
ctx.body = 'Internal Server Error'
// 记录完整错误信息
console.error(err.stack)
}
}
})
错误对象的标准化
创建自定义错误类可以使错误处理更一致。扩展Error类可以添加额外属性如statusCode、isOperational等。
class AppError extends Error {
constructor(message, statusCode) {
super(message)
this.statusCode = statusCode
this.isOperational = true
Error.captureStackTrace(this, this.constructor)
}
}
// 使用示例
throw new AppError('Invalid user input', 400)
异步错误的特殊处理
Koa2基于async/await,但某些异步操作如setTimeout中的错误不会被自动捕获。需要特别注意处理这类情况。
app.use(async (ctx, next) => {
try {
// 模拟异步操作
await new Promise((resolve, reject) => {
setTimeout(() => {
try {
someAsyncOperation()
resolve()
} catch (err) {
reject(err)
}
}, 100)
})
await next()
} catch (err) {
// 错误处理
}
})
错误日志记录策略
完善的错误处理应该包含日志记录。可以使用winston、pino等日志库,记录错误堆栈、请求上下文等信息。
const logger = require('winston')
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
logger.error({
message: err.message,
stack: err.stack,
url: ctx.url,
method: ctx.method
})
// 其他错误处理逻辑
}
})
开发与生产环境的差异处理
开发环境通常需要详细的错误信息,而生产环境应该隐藏敏感信息。
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
ctx.status = err.status || 500
if (process.env.NODE_ENV === 'development') {
ctx.body = {
message: err.message,
stack: err.stack
}
} else {
ctx.body = { message: 'Something went wrong' }
}
}
})
错误事件的发射与监听
Koa2应用可以发射error事件,便于集中处理错误如发送报警邮件。
app.on('error', (err, ctx) => {
// 发送邮件通知管理员
if (err.status >= 500) {
sendAlertEmail(err, ctx)
}
})
// 中间件中
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
ctx.app.emit('error', err, ctx)
// 其他处理
}
})
请求验证错误的处理
对于请求体验证错误,可以使用joi等库验证后统一格式返回。
const Joi = require('joi')
app.use(async (ctx, next) => {
try {
const schema = Joi.object({
username: Joi.string().required(),
password: Joi.string().min(6).required()
})
const { error } = schema.validate(ctx.request.body)
if (error) {
throw new AppError(error.details[0].message, 400)
}
await next()
} catch (err) {
// 错误处理
}
})
数据库错误的处理
数据库操作错误需要特殊处理,如连接失败、查询超时等。
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
// MongoDB错误
if (err.name === 'MongoError') {
if (err.code === 11000) {
ctx.status = 409
ctx.body = { error: 'Duplicate key error' }
} else {
ctx.status = 503
ctx.body = { error: 'Database error' }
}
}
// 其他错误处理
}
})
性能监控与错误关联
将错误与性能指标关联可以帮助分析问题原因。可以使用APM工具如New Relic。
app.use(async (ctx, next) => {
const start = Date.now()
try {
await next()
const duration = Date.now() - start
recordResponseTime(ctx.path, duration)
} catch (err) {
const duration = Date.now() - start
recordErrorResponseTime(ctx.path, duration, err)
throw err
}
})
客户端错误响应的格式化
统一的错误响应格式有助于客户端处理。可以包含错误代码、消息、详情等字段。
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
ctx.status = err.status || 500
ctx.body = {
code: err.code || 'UNKNOWN_ERROR',
message: err.message,
details: process.env.NODE_ENV === 'development' ? err.stack : undefined,
timestamp: new Date().toISOString()
}
}
})
中间件执行顺序的影响
错误处理中间件的位置很重要,它应该在其他中间件之前注册,但要在日志、请求ID等基础中间件之后。
// 正确的注册顺序
app.use(requestIdMiddleware)
app.use(loggerMiddleware)
app.use(errorHandlerMiddleware)
app.use(bodyParser())
app.use(router.routes())
测试错误处理中间件
编写测试确保错误处理中间件按预期工作,包括模拟各种错误场景。
const request = require('supertest')
const app = require('../app')
describe('Error Handler', () => {
it('should handle 404 errors', async () => {
const res = await request(app).get('/nonexistent')
expect(res.status).toBe(404)
expect(res.body.code).toBe('NOT_FOUND')
})
it('should handle 500 errors', async () => {
// 模拟路由抛出错误
app.use('/test-error', ctx => {
throw new Error('Test error')
})
const res = await request(app).get('/test-error')
expect(res.status).toBe(500)
expect(res.body.code).toBe('INTERNAL_SERVER_ERROR')
})
})
错误处理与HTTP状态码
合理使用HTTP状态码能帮助客户端理解错误性质。常见状态码及其适用场景:
- 400 Bad Request: 客户端请求错误
- 401 Unauthorized: 需要认证
- 403 Forbidden: 无权限
- 404 Not Found: 资源不存在
- 429 Too Many Requests: 请求过多
- 500 Internal Server Error: 服务器内部错误
- 503 Service Unavailable: 服务不可用
app.use(async (ctx, next) => {
try {
await next()
if (ctx.status === 404) {
throw new AppError('Not Found', 404)
}
} catch (err) {
// 确保状态码正确设置
ctx.status = err.status || 500
// 其他处理
}
})
错误处理中间件的可配置性
通过配置对象使错误处理中间件更灵活,可以配置是否显示堆栈、自定义格式化函数等。
function createErrorHandler(options = {}) {
const defaults = {
showStack: process.env.NODE_ENV === 'development',
formatError: err => ({
code: err.code,
message: err.message
})
}
const config = { ...defaults, ...options }
return async (ctx, next) => {
try {
await next()
} catch (err) {
ctx.status = err.status || 500
ctx.body = config.formatError(err)
if (config.showStack) {
ctx.body.stack = err.stack
}
}
}
}
// 使用
app.use(createErrorHandler({
showStack: true
}))
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:常用官方中间件介绍与使用
下一篇:第三方中间件的选择与评估