Error handling middleware writing techniques
Basic Concepts of Error Handling Middleware
Error handling middleware is the central place for catching and handling errors in Koa2 applications. It is typically registered as the first middleware to ensure it can catch any errors thrown by subsequent middleware. In Koa2, error handling middleware wraps yield next
within a try/catch block.
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)
}
})
Error Classification and Handling Strategies
Errors in Koa2 applications can be broadly categorized into three types: operational errors, programming errors, and third-party errors. Operational errors, such as invalid user input, should return 4xx status codes; programming errors, like undefined variables, should return 500 status codes; and third-party errors, such as database connection failures, require specific handling based on the situation.
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
// Operational errors
if (err.isOperational) {
ctx.status = err.statusCode || 400
ctx.body = { error: err.message }
}
// Programming errors
else {
ctx.status = 500
ctx.body = 'Internal Server Error'
// Log the full error details
console.error(err.stack)
}
}
})
Standardization of Error Objects
Creating custom error classes can make error handling more consistent. Extending the Error
class allows adding extra properties like statusCode
, isOperational
, etc.
class AppError extends Error {
constructor(message, statusCode) {
super(message)
this.statusCode = statusCode
this.isOperational = true
Error.captureStackTrace(this, this.constructor)
}
}
// Usage example
throw new AppError('Invalid user input', 400)
Special Handling of Asynchronous Errors
Koa2 is based on async/await, but certain asynchronous operations, such as errors in setTimeout
, are not automatically caught. Special attention is needed for such cases.
app.use(async (ctx, next) => {
try {
// Simulate an asynchronous operation
await new Promise((resolve, reject) => {
setTimeout(() => {
try {
someAsyncOperation()
resolve()
} catch (err) {
reject(err)
}
}, 100)
})
await next()
} catch (err) {
// Error handling
}
})
Error Logging Strategies
A robust error handling system should include logging. Libraries like winston
or pino
can be used to log error stacks, request context, and other details.
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
})
// Additional error handling logic
}
})
Differences Between Development and Production Environments
Development environments typically require detailed error information, while production environments should hide sensitive details.
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' }
}
}
})
Emitting and Listening to Error Events
Koa2 applications can emit error
events, making it easier to centralize error handling, such as sending alert emails.
app.on('error', (err, ctx) => {
// Send an email notification to the admin
if (err.status >= 500) {
sendAlertEmail(err, ctx)
}
})
// In middleware
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
ctx.app.emit('error', err, ctx)
// Additional handling
}
})
Handling Request Validation Errors
For request validation errors, libraries like joi
can be used to validate and return a unified error format.
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) {
// Error handling
}
})
Handling Database Errors
Database operation errors, such as connection failures or query timeouts, require special handling.
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
// MongoDB errors
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' }
}
}
// Other error handling
}
})
Performance Monitoring and Error Correlation
Correlating errors with performance metrics can help analyze root causes. Tools like New Relic can be used for this purpose.
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
}
})
Formatting Client Error Responses
A unified error response format helps clients handle errors more effectively. It can include fields like error code, message, and details.
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()
}
}
})
Impact of Middleware Execution Order
The position of error handling middleware is crucial. It should be registered before other middleware but after foundational middleware like logging and request ID generation.
// Correct registration order
app.use(requestIdMiddleware)
app.use(loggerMiddleware)
app.use(errorHandlerMiddleware)
app.use(bodyParser())
app.use(router.routes())
Testing Error Handling Middleware
Write tests to ensure the error handling middleware works as expected, including simulating various error scenarios.
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 () => {
// Simulate a route throwing an error
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')
})
})
Error Handling and HTTP Status Codes
Proper use of HTTP status codes helps clients understand the nature of errors. Common status codes and their use cases:
- 400 Bad Request: Client request error
- 401 Unauthorized: Authentication required
- 403 Forbidden: No permission
- 404 Not Found: Resource not found
- 429 Too Many Requests: Too many requests
- 500 Internal Server Error: Server internal error
- 503 Service Unavailable: Service unavailable
app.use(async (ctx, next) => {
try {
await next()
if (ctx.status === 404) {
throw new AppError('Not Found', 404)
}
} catch (err) {
// Ensure the status code is set correctly
ctx.status = err.status || 500
// Additional handling
}
})
Configurability of Error Handling Middleware
Make error handling middleware more flexible by using a configuration object to control settings like stack trace visibility and custom formatting functions.
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
}
}
}
}
// Usage
app.use(createErrorHandler({
showStack: true
}))
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:常用官方中间件介绍与使用
下一篇:第三方中间件的选择与评估