错误处理机制与调试
错误处理机制与调试
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
上一篇:静态文件服务与资源托管
下一篇:Cookie与Session管理