Skip to content
On this page

Koa错误处理

错误处理是Koa应用开发中的关键部分。Koa提供了多层次的错误处理机制,从中间件内部错误处理到全局错误处理,确保应用的稳定性和可靠性。

Koa错误处理概述

Koa的错误处理基于async/await,使得错误处理更加直观和可控。Koa应用会捕获中间件中抛出的错误并提供全局错误处理机制。

中间件内错误处理

基础错误捕获

javascript
app.use(async (ctx, next) => {
  try {
    await next(); // 将控制权传递给下一个中间件
  } catch (err) {
    // 处理错误
    ctx.status = err.status || 500;
    ctx.body = {
      error: err.message
    };
    
    // 记录错误
    console.error('Middleware error:', err);
  }
});

具体错误处理示例

javascript
app.use(async (ctx, next) => {
  try {
    // 业务逻辑
    const user = await findUser(ctx.params.id);
    
    if (!user) {
      // 抛出自定义错误
      const err = new Error('User not found');
      err.status = 404;
      throw err;
    }
    
    ctx.body = user;
    await next();
  } catch (err) {
    // 根据错误类型进行不同处理
    if (err.status === 404) {
      ctx.status = 404;
      ctx.body = { error: 'User not found' };
    } else if (err.status === 400) {
      ctx.status = 400;
      ctx.body = { error: 'Bad request', details: err.message };
    } else {
      // 未知错误
      ctx.status = 500;
      ctx.body = { error: 'Internal server error' };
    }
    
    console.error('Request error:', err);
  }
});

全局错误处理

应用级错误事件

javascript
const app = new Koa();

// 监听应用错误事件
app.on('error', (err, ctx) => {
  console.error('Application error:', err);
  
  // 发送错误到监控服务
  if (process.env.NODE_ENV === 'production') {
    // 发送到错误监控服务,如Sentry
    // errorReportingService.captureException(err, { context: ctx });
  }
});

app.use(async (ctx, next) => {
  await next();
});

app.listen(3000);

统一错误处理中间件

javascript
const errorHandler = async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    // 设置错误状态
    ctx.status = err.status || 500;
    
    // 根据环境返回不同错误信息
    if (ctx.app.env === 'development') {
      // 开发环境返回详细错误信息
      ctx.body = {
        error: err.message,
        stack: err.stack,
        status: ctx.status
      };
    } else {
      // 生产环境返回通用错误信息
      ctx.body = {
        error: ctx.status === 500 ? 'Internal Server Error' : err.message,
        status: ctx.status
      };
    }
    
    // 记录错误
    ctx.app.emit('error', err, ctx);
  }
};

app.use(errorHandler);

自定义错误类

创建应用错误类

javascript
class AppError extends Error {
  constructor(message, statusCode, isOperational = true) {
    super(message);
    
    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = isOperational; // 标识是否为可操作错误
    
    Error.captureStackTrace(this, this.constructor);
  }
}

// 具体错误类型
class ValidationError extends AppError {
  constructor(message) {
    super(message, 400);
    this.type = 'VALIDATION_ERROR';
  }
}

class NotFoundError extends AppError {
  constructor(message = 'Resource not found') {
    super(message, 404);
    this.type = 'NOT_FOUND_ERROR';
  }
}

class UnauthorizedError extends AppError {
  constructor(message = 'Unauthorized') {
    super(message, 401);
    this.type = 'UNAUTHORIZED_ERROR';
  }
}

class ForbiddenError extends AppError {
  constructor(message = 'Forbidden') {
    super(message, 403);
    this.type = 'FORBIDDEN_ERROR';
  }
}

使用自定义错误

javascript
app.use(async (ctx, next) => {
  try {
    if (ctx.path === '/protected' && !ctx.state.user) {
      throw new UnauthorizedError('Authentication required');
    }
    
    if (ctx.path.startsWith('/admin') && ctx.state.user.role !== 'admin') {
      throw new ForbiddenError('Admin access required');
    }
    
    await next();
  } catch (err) {
    if (err instanceof AppError) {
      ctx.status = err.statusCode;
      ctx.body = {
        status: err.status,
        message: err.message,
        ...(ctx.app.env === 'development' && { stack: err.stack })
      };
    } else {
      // 未知错误
      ctx.status = 500;
      ctx.body = { status: 'error', message: 'Something went wrong' };
    }
    
    ctx.app.emit('error', err, ctx);
  }
});

异步操作错误处理

异步中间件错误

javascript
// 正确的异步错误处理
app.use(async (ctx, next) => {
  try {
    // 异步操作
    const data = await asyncOperation();
    ctx.body = data;
    await next();
  } catch (err) {
    // 异步错误会被正确捕获
    ctx.status = 500;
    ctx.body = { error: err.message };
  }
});

// 错误的做法 - 会导致未处理的Promise拒绝
app.use((ctx, next) => {
  asyncOperation()
    .then(data => {
      ctx.body = data;
    })
    .catch(err => {
      // 这个错误可能不会被正确处理
      ctx.status = 500;
      ctx.body = { error: err.message };
    });
});

数据库操作错误处理

javascript
const handleDbError = (err) => {
  if (err.name === 'ValidationError') {
    return new ValidationError('Data validation failed');
  }
  
  if (err.name === 'CastError') {
    return new ValidationError('Invalid ID format');
  }
  
  if (err.code === 11000) {
    return new ValidationError('Duplicate key error');
  }
  
  // 返回原始错误
  return err;
};

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    // 处理数据库错误
    const processedErr = handleDbError(err);
    
    if (processedErr !== err) {
      ctx.status = processedErr.statusCode;
      ctx.body = { error: processedErr.message };
    } else {
      // 未处理的错误
      ctx.status = 500;
      ctx.body = { error: 'Database error occurred' };
    }
    
    ctx.app.emit('error', err, ctx);
  }
});

错误日志和监控

结构化错误日志

javascript
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.splat(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

// 错误处理中间件集成日志
const loggingErrorHandler = async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    
    // 记录错误日志
    logger.error({
      message: err.message,
      stack: err.stack,
      status: ctx.status,
      method: ctx.method,
      url: ctx.url,
      ip: ctx.ip,
      userAgent: ctx.get('User-Agent'),
      timestamp: new Date().toISOString()
    });
    
    // 返回错误响应
    ctx.body = {
      error: ctx.status === 500 ? 'Internal Server Error' : err.message,
      status: ctx.status
    };
    
    // 发出错误事件
    ctx.app.emit('error', err, ctx);
  }
};

app.use(loggingErrorHandler);

404错误处理

404处理中间件

javascript
// 404处理应该在所有路由之后
app.use(async (ctx, next) => {
  await next(); // 让其他中间件先处理
  
  // 如果状态仍然是404且没有响应体,则是未找到路由
  if (ctx.status === 404 && !ctx.body) {
    ctx.status = 404;
    ctx.body = {
      error: 'Not Found',
      path: ctx.path,
      method: ctx.method
    };
  }
});

// 或者使用更具体的404处理
app.use(async (ctx, next) => {
  try {
    await next();
    
    // 如果没有找到路由
    if (ctx.status === 404) {
      ctx.throw(404, `Route ${ctx.path} not found`);
    }
  } catch (err) {
    if (err.status === 404) {
      ctx.status = 404;
      ctx.body = {
        error: 'Resource not found',
        requestedPath: ctx.path
      };
    } else {
      throw err; // 重新抛出其他错误
    }
  }
});

错误处理最佳实践

1. 分层错误处理

javascript
// 应用层错误处理
const appErrorHandler = async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = {
      status: err.status >= 500 ? 'error' : 'fail',
      message: err.expose ? err.message : 'Internal server error'
    };
    ctx.app.emit('error', err, ctx);
  }
};

// 业务逻辑层错误处理
const businessErrorHandler = async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    if (err.type === 'BUSINESS_ERROR') {
      ctx.status = err.statusCode;
      ctx.body = {
        status: 'fail',
        message: err.message,
        code: err.code
      };
    } else {
      throw err; // 让上层处理其他错误
    }
  }
};

// 按顺序应用错误处理中间件
app.use(appErrorHandler);
app.use(businessErrorHandler);

2. 环境特定错误处理

javascript
const createErrorHandler = (env) => {
  return async (ctx, next) => {
    try {
      await next();
    } catch (err) {
      ctx.status = err.status || 500;
      
      if (env === 'development') {
        // 开发环境返回详细错误
        ctx.body = {
          error: {
            message: err.message,
            stack: err.stack,
            status: ctx.status
          }
        };
      } else {
        // 生产环境返回安全错误信息
        ctx.body = {
          error: {
            message: ctx.status < 500 ? err.message : 'Internal server error',
            status: ctx.status
          }
        };
        
        // 在生产环境中记录完整错误
        ctx.app.emit('error', err, ctx);
      }
    }
  };
};

app.use(createErrorHandler(app.env));

3. 错误分类处理

javascript
const errorTypes = {
  VALIDATION: 'VALIDATION_ERROR',
  AUTHENTICATION: 'AUTHENTICATION_ERROR',
  AUTHORIZATION: 'AUTHORIZATION_ERROR',
  NOT_FOUND: 'NOT_FOUND_ERROR',
  DATABASE: 'DATABASE_ERROR',
  NETWORK: 'NETWORK_ERROR'
};

const categorizedErrorHandler = async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    // 根据错误类型进行处理
    switch (err.type) {
      case errorTypes.VALIDATION:
        ctx.status = 400;
        ctx.body = {
          error: 'Validation failed',
          details: err.details,
          status: 'fail'
        };
        break;
        
      case errorTypes.AUTHENTICATION:
        ctx.status = 401;
        ctx.body = {
          error: 'Authentication required',
          status: 'fail'
        };
        break;
        
      case errorTypes.AUTHORIZATION:
        ctx.status = 403;
        ctx.body = {
          error: 'Insufficient permissions',
          status: 'fail'
        };
        break;
        
      case errorTypes.NOT_FOUND:
        ctx.status = 404;
        ctx.body = {
          error: err.message || 'Resource not found',
          status: 'fail'
        };
        break;
        
      default:
        ctx.status = 500;
        ctx.body = {
          error: 'Internal server error',
          status: 'error'
        };
    }
    
    ctx.app.emit('error', err, ctx);
  }
};

app.use(categorizedErrorHandler);

未捕获异常处理

进程级错误处理

javascript
// 处理未捕获的异常
process.on('uncaughtException', (err) => {
  console.error('Uncaught Exception:', err);
  process.exit(1); // 退出进程
});

// 处理未处理的Promise拒绝
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  process.exit(1); // 退出进程
});

// SIGTERM信号处理
process.on('SIGTERM', () => {
  console.log('Received SIGTERM, shutting down gracefully');
  server.close(() => {
    console.log('Process terminated');
  });
});

通过完善的错误处理机制,可以确保Koa应用在面对各种错误情况时都能做出适当的响应,提供良好的用户体验和系统稳定性。