阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 错误处理中间件的编写技巧

错误处理中间件的编写技巧

作者:陈川 阅读数:19412人阅读 分类: Node.js

错误处理中间件的基本概念

错误处理中间件是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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌