Koa 的错误处理机制非常优雅,通过 try-catch 和事件系统提供了多种错误处理方式。正确处理错误是构建健壮应用的关键。
1. 使用 ctx.throw() 抛出错误:
javascriptapp.use(async (ctx) => { if (!ctx.query.token) { ctx.throw(401, 'Token is required'); } ctx.body = 'Success'; });
2. 使用 try-catch 捕获错误:
javascriptapp.use(async (ctx, next) => { try { await next(); } catch (err) { ctx.status = err.status || 500; ctx.body = { error: err.message, code: err.code || 'INTERNAL_ERROR' }; ctx.app.emit('error', err, ctx); } });
3. 错误处理中间件:
javascriptasync function errorHandler(ctx, next) { try { await next(); } catch (err) { ctx.status = err.status || 500; // 开发环境返回详细错误信息 if (app.env === 'development') { ctx.body = { error: err.message, stack: err.stack, code: err.code }; } else { // 生产环境返回简化的错误信息 ctx.body = { error: 'Internal Server Error', code: 'INTERNAL_ERROR' }; } // 触发应用错误事件 ctx.app.emit('error', err, ctx); } } app.use(errorHandler);
4. 自定义错误类:
javascriptclass AppError extends Error { constructor(status, message, code) { super(message); this.status = status; this.code = code; this.name = 'AppError'; } } class NotFoundError extends AppError { constructor(message = 'Resource not found') { super(404, message, 'NOT_FOUND'); this.name = 'NotFoundError'; } } class ValidationError extends AppError { constructor(message = 'Validation failed') { super(400, message, 'VALIDATION_ERROR'); this.name = 'ValidationError'; } } // 使用自定义错误 app.use(async (ctx) => { const user = await findUser(ctx.params.id); if (!user) { throw new NotFoundError('User not found'); } ctx.body = user; });
5. 全局错误监听:
javascriptapp.on('error', (err, ctx) => { // 记录错误日志 console.error('Server error:', err); // 发送错误通知(如邮件、Slack等) sendErrorNotification(err, ctx); // 上报错误到监控系统 reportToMonitoring(err, ctx); });
6. 404 处理:
javascript// 在所有路由之后添加 404 处理 app.use(async (ctx) => { ctx.status = 404; ctx.body = { error: 'Not Found', code: 'NOT_FOUND', path: ctx.url }; });
7. 异步错误处理:
javascript// Koa 会自动捕获 async 函数中的错误 app.use(async (ctx) => { const data = await fetchData(); // 如果这里抛出错误,会被捕获 ctx.body = data; }); // 对于 Promise 链,确保正确处理 app.use(async (ctx) => { try { const result = await someAsyncOperation() .then(data => processData(data)) .catch(err => { throw new AppError(400, 'Processing failed', 'PROCESS_ERROR'); }); ctx.body = result; } catch (err) { ctx.throw(err.status || 500, err.message); } });
8. 错误处理最佳实践:
javascript// 完整的错误处理示例 const Koa = require('koa'); const app = new Koa(); // 自定义错误类 class AppError extends Error { constructor(status, message, code) { super(message); this.status = status; this.code = code; } } // 错误处理中间件 app.use(async (ctx, next) => { try { await next(); } catch (err) { // 设置状态码 ctx.status = err.status || 500; // 构建错误响应 const errorResponse = { error: err.message, code: err.code || 'INTERNAL_ERROR', timestamp: new Date().toISOString() }; // 开发环境包含堆栈信息 if (app.env === 'development') { errorResponse.stack = err.stack; } ctx.body = errorResponse; // 触发错误事件 ctx.app.emit('error', err, ctx); } }); // 全局错误监听 app.on('error', (err, ctx) => { console.error(`[${new Date().toISOString()}] Error:`, err.message); console.error('Path:', ctx.url); console.error('Stack:', err.stack); }); // 业务路由 app.use(async (ctx) => { if (ctx.path === '/error') { throw new AppError(500, 'Something went wrong', 'SERVER_ERROR'); } if (ctx.path === '/not-found') { ctx.throw(404, 'Resource not found'); } ctx.body = 'Hello Koa'; }); app.listen(3000);
9. 常见错误处理场景:
javascript// 数据库错误处理 app.use(async (ctx, next) => { try { await next(); } catch (err) { if (err.code === '23505') { // PostgreSQL 唯一约束冲突 ctx.throw(409, 'Resource already exists'); } else if (err.code === '23503') { // 外键约束冲突 ctx.throw(400, 'Invalid reference'); } else { throw err; } } }); // 验证错误处理 app.use(async (ctx, next) => { try { await next(); } catch (err) { if (err.name === 'ValidationError') { ctx.throw(400, err.message); } throw err; } });
错误处理要点:
- 使用 try-catch 包裹可能出错的代码
- 创建自定义错误类,提供更清晰的错误信息
- 区分开发环境和生产环境的错误响应
- 实现全局错误监听,统一记录和上报
- 为不同类型的错误提供适当的 HTTP 状态码
- 确保错误不会泄露敏感信息
- 在洋葱模型的最外层添加错误处理中间件