阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 错误处理机制与调试

错误处理机制与调试

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

错误处理机制与调试

Express框架提供了多种方式来处理错误和调试应用程序。无论是同步错误还是异步错误,都需要合理的捕获和处理,否则可能导致应用崩溃或不可预测的行为。错误处理中间件、try-catch块、全局错误处理器等都是常用的手段。

同步错误处理

同步代码中的错误可以通过try-catch块直接捕获。在Express路由处理程序中,同步错误如果没有被捕获,会被框架自动捕获并传递给错误处理中间件。

app.get('/sync-error', (req, res) => {
  try {
    // 同步操作可能抛出错误
    const data = JSON.parse('invalid json')
    res.send(data)
  } catch (err) {
    // 捕获同步错误
    res.status(400).send({ error: err.message })
  }
})

异步错误处理

处理异步错误需要特别注意,因为try-catch无法捕获回调函数或Promise链中抛出的错误。Express 5.x开始支持异步函数中未捕获错误的自动处理,但在4.x版本中需要显式处理。

// Express 4.x中的异步错误处理
app.get('/async-error', (req, res, next) => {
  someAsyncFunction((err, data) => {
    if (err) return next(err) // 将错误传递给错误处理中间件
    res.send(data)
  })
})

// 使用async/await的更好方式
app.get('/async-await', async (req, res, next) => {
  try {
    const data = await somePromiseFunction()
    res.send(data)
  } catch (err) {
    next(err) // 将错误传递给错误处理中间件
  }
})

错误处理中间件

Express中的错误处理中间件与其他中间件类似,但接受四个参数(err, req, res, next)。定义错误处理中间件时,应该放在所有其他中间件和路由之后。

// 基本错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

// 更完善的错误处理
app.use((err, req, res, next) => {
  const statusCode = err.statusCode || 500
  const message = statusCode === 500 ? 'Internal Server Error' : err.message
  
  if (process.env.NODE_ENV === 'development') {
    res.status(statusCode).json({
      error: message,
      stack: err.stack
    })
  } else {
    res.status(statusCode).json({ error: message })
  }
})

自定义错误类

创建自定义错误类可以更好地组织和区分不同类型的错误,便于错误处理和调试。

class AppError extends Error {
  constructor(message, statusCode) {
    super(message)
    this.statusCode = statusCode
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'
    this.isOperational = true
    Error.captureStackTrace(this, this.constructor)
  }
}

// 使用自定义错误
app.get('/custom-error', (req, res, next) => {
  if (!req.query.id) {
    return next(new AppError('ID is required', 400))
  }
  // ...
})

调试技巧

调试Express应用可以使用多种工具和技术。Node.js内置的调试器、console.log、专门的调试模块等都是常用方法。

// 使用debug模块
const debug = require('debug')('app:server')
app.get('/debug', (req, res) => {
  debug('Request received for /debug')
  // ...
})

// 使用Node.js内置inspector
// 启动应用时使用:node --inspect app.js
// 然后在Chrome中访问 chrome://inspect

日志记录

良好的日志记录系统对于错误追踪和调试至关重要。可以使用winston、morgan等日志库。

const winston = require('winston')
const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
})

// 在错误处理中间件中使用
app.use((err, req, res, next) => {
  logger.error(err.stack)
  res.status(500).send('Error occurred')
})

处理未捕获异常和未处理的Promise拒绝

除了路由中的错误,还需要处理全局未捕获的异常和未处理的Promise拒绝。

// 处理未捕获异常
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)
})

使用中间件增强错误处理

一些第三方中间件可以增强Express的错误处理能力,如express-async-errors可以简化async/await的错误处理。

require('express-async-errors') // 应该在引入express后立即调用

// 现在可以省略try-catch块
app.get('/simplified', async (req, res) => {
  const data = await someAsyncOperation()
  res.send(data)
  // 任何错误会自动传递给错误处理中间件
})

HTTP状态码的正确使用

合理的HTTP状态码对于API错误处理非常重要,可以帮助客户端理解错误性质。

app.get('/status-codes', (req, res, next) => {
  if (!req.query.token) {
    return res.status(401).json({ error: 'Unauthorized' }) // 401未授权
  }
  
  if (!req.query.id) {
    return res.status(400).json({ error: 'ID is required' }) // 400错误请求
  }
  
  try {
    const resource = findResource(req.query.id)
    if (!resource) {
      return res.status(404).json({ error: 'Not found' }) // 404未找到
    }
    res.json(resource)
  } catch (err) {
    next(err) // 500内部服务器错误
  }
})

性能考虑

错误处理逻辑本身也可能成为性能瓶颈,特别是在高流量应用中。

// 避免在热路径中进行昂贵的错误处理
app.use((err, req, res, next) => {
  // 快速返回基本错误响应
  res.status(err.statusCode || 500).json({ error: err.message })
  
  // 异步记录详细错误信息,不影响响应时间
  setImmediate(() => {
    logger.error({
      message: err.message,
      stack: err.stack,
      request: {
        method: req.method,
        url: req.url,
        headers: req.headers
      }
    })
  })
})

测试错误处理

确保错误处理逻辑按预期工作同样重要,应该为错误处理编写测试用例。

// 使用测试框架如Jest测试错误处理
describe('Error Handling', () => {
  it('should return 404 for unknown routes', async () => {
    const response = await request(app).get('/nonexistent')
    expect(response.statusCode).toBe(404)
  })
  
  it('should handle async errors', async () => {
    const response = await request(app).get('/async-error')
    expect(response.statusCode).toBe(500)
    expect(response.body.error).toBeDefined()
  })
})

安全考虑

错误响应中暴露的信息需要谨慎处理,避免泄露敏感信息。

app.use((err, req, res, next) => {
  // 生产环境中隐藏技术细节
  const response = {
    error: 'An error occurred'
  }
  
  if (process.env.NODE_ENV === 'development') {
    response.message = err.message
    response.stack = err.stack
  }
  
  res.status(500).json(response)
})

客户端错误处理

对于Web应用,还需要考虑如何将服务器错误优雅地呈现给客户端。

// 前端处理API错误的示例
fetch('/api/data')
  .then(response => {
    if (!response.ok) {
      return response.json().then(err => {
        // 根据状态码显示不同错误信息
        if (response.status === 401) {
          showLoginModal()
        } else {
          showErrorToast(err.error || 'Request failed')
        }
        throw err
      })
    }
    return response.json()
  })
  .then(data => renderData(data))
  .catch(error => {
    console.error('API request failed:', error)
  })

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

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