错误处理 #
一、错误处理概述 #
Fastify提供了完善的错误处理机制,包括自动错误捕获、自定义错误处理器和标准化错误响应。
1.1 错误类型 #
| 错误类型 | 说明 | 状态码 |
|---|---|---|
| 验证错误 | Schema验证失败 | 400 |
| 认证错误 | 未授权访问 | 401 |
| 权限错误 | 无权限访问 | 403 |
| 资源错误 | 资源不存在 | 404 |
| 服务器错误 | 内部错误 | 500 |
1.2 错误处理流程 #
text
错误发生
│
▼
┌─────────────────┐
│ onError钩子 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 错误处理器 │
│ setErrorHandler │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 发送响应 │
└─────────────────┘
二、基本错误处理 #
2.1 抛出错误 #
javascript
fastify.get('/error', async (request, reply) => {
throw new Error('Something went wrong')
})
响应:
json
{
"statusCode": 500,
"error": "Internal Server Error",
"message": "Something went wrong"
}
2.2 带状态码的错误 #
javascript
fastify.get('/not-found', async (request, reply) => {
const error = new Error('User not found')
error.statusCode = 404
throw error
})
2.3 使用@fastify/sensible #
javascript
fastify.register(require('@fastify/sensible'))
fastify.get('/bad-request', async (request, reply) => {
throw fastify.httpErrors.badRequest('Invalid parameters')
})
fastify.get('/unauthorized', async (request, reply) => {
throw fastify.httpErrors.unauthorized('Token required')
})
fastify.get('/forbidden', async (request, reply) => {
throw fastify.httpErrors.forbidden('Access denied')
})
fastify.get('/not-found', async (request, reply) => {
throw fastify.httpErrors.notFound('User not found')
})
fastify.get('/internal-error', async (request, reply) => {
throw fastify.httpErrors.internalServerError('Database error')
})
三、自定义错误处理器 #
3.1 基本错误处理器 #
javascript
fastify.setErrorHandler((error, request, reply) => {
reply.code(error.statusCode || 500).send({
statusCode: error.statusCode || 500,
error: error.name,
message: error.message
})
})
3.2 处理验证错误 #
javascript
fastify.setErrorHandler((error, request, reply) => {
if (error.validation) {
reply.code(400).send({
statusCode: 400,
error: 'Validation Error',
message: error.message,
details: error.validation
})
return
}
reply.send(error)
})
3.3 环境区分 #
javascript
fastify.setErrorHandler((error, request, reply) => {
const statusCode = error.statusCode || 500
const response = {
statusCode,
error: error.name,
message: statusCode === 500 && process.env.NODE_ENV === 'production'
? 'Internal Server Error'
: error.message
}
if (process.env.NODE_ENV !== 'production') {
response.stack = error.stack
}
reply.code(statusCode).send(response)
})
3.4 完整错误处理器 #
javascript
fastify.setErrorHandler((error, request, reply) => {
const statusCode = error.statusCode || 500
if (error.validation) {
reply.code(400).send({
success: false,
statusCode: 400,
error: 'Validation Error',
message: error.message,
details: error.validation,
requestId: request.id
})
return
}
if (statusCode >= 500) {
fastify.log.error({
error: error.message,
stack: error.stack,
requestId: request.id
})
}
reply.code(statusCode).send({
success: false,
statusCode,
error: error.name,
message: statusCode >= 500 && process.env.NODE_ENV === 'production'
? 'Internal Server Error'
: error.message,
requestId: request.id
})
})
四、自定义错误类 #
4.1 基本错误类 #
javascript
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message)
this.name = this.constructor.name
this.statusCode = statusCode
this.isOperational = true
Error.captureStackTrace(this, this.constructor)
}
}
class NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(message, 404)
}
}
class ValidationError extends AppError {
constructor(message = 'Validation failed') {
super(message, 400)
}
}
class UnauthorizedError extends AppError {
constructor(message = 'Unauthorized') {
super(message, 401)
}
}
class ForbiddenError extends AppError {
constructor(message = 'Forbidden') {
super(message, 403)
}
}
class ConflictError extends AppError {
constructor(message = 'Conflict') {
super(message, 409)
}
}
module.exports = {
AppError,
NotFoundError,
ValidationError,
UnauthorizedError,
ForbiddenError,
ConflictError
}
4.2 使用自定义错误 #
javascript
const { NotFoundError, ValidationError } = require('./errors')
fastify.get('/users/:id', async (request, reply) => {
const user = await findUser(request.params.id)
if (!user) {
throw new NotFoundError('User not found')
}
return user
})
fastify.post('/users', async (request, reply) => {
if (!request.body.email) {
throw new ValidationError('Email is required')
}
return createUser(request.body)
})
4.3 错误处理器适配 #
javascript
const { AppError } = require('./errors')
fastify.setErrorHandler((error, request, reply) => {
if (error instanceof AppError) {
reply.code(error.statusCode).send({
success: false,
statusCode: error.statusCode,
error: error.name,
message: error.message
})
return
}
if (error.validation) {
reply.code(400).send({
success: false,
statusCode: 400,
error: 'ValidationError',
message: error.message,
details: error.validation
})
return
}
fastify.log.error(error)
reply.code(500).send({
success: false,
statusCode: 500,
error: 'InternalServerError',
message: process.env.NODE_ENV === 'production'
? 'Something went wrong'
: error.message
})
})
五、错误钩子 #
5.1 onError钩子 #
javascript
fastify.addHook('onError', async (request, reply, error) => {
console.error('Error occurred:', error.message)
})
5.2 错误日志 #
javascript
fastify.addHook('onError', async (request, reply, error) => {
fastify.log.error({
requestId: request.id,
method: request.method,
url: request.url,
error: {
name: error.name,
message: error.message,
stack: error.stack
}
})
})
5.3 错误转换 #
javascript
fastify.addHook('onError', async (request, reply, error) => {
if (error.name === 'MongoError' && error.code === 11000) {
error.statusCode = 409
error.message = 'Duplicate key error'
}
if (error.name === 'JsonWebTokenError') {
error.statusCode = 401
error.message = 'Invalid token'
}
})
六、路由级错误处理 #
6.1 路由错误处理器 #
javascript
fastify.get('/custom-error', {
errorHandler: (error, request, reply) => {
reply.code(400).send({
custom: true,
message: error.message
})
}
}, async (request, reply) => {
throw new Error('Custom error handling')
})
6.2 绕过错误处理器 #
javascript
fastify.get('/bypass', async (request, reply) => {
try {
await riskyOperation()
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
})
七、验证错误处理 #
7.1 Schema验证错误 #
javascript
fastify.post('/users', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' }
}
}
}
}, async (request, reply) => {
return { created: true }
})
验证失败响应:
json
{
"statusCode": 400,
"error": "Bad Request",
"message": "body must have required property 'name'"
}
7.2 自定义验证错误消息 #
javascript
fastify.setErrorHandler((error, request, reply) => {
if (error.validation) {
const messages = error.validation.map(v => {
const path = v.instancePath || v.params.missingProperty
return `${path} ${v.message}`
})
reply.code(400).send({
statusCode: 400,
error: 'Validation Error',
messages
})
return
}
reply.send(error)
})
八、异步错误处理 #
8.1 未捕获的Promise拒绝 #
javascript
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection:', reason)
})
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error)
process.exit(1)
})
8.2 优雅关闭 #
javascript
const closeListeners = [
'beforeExit',
'SIGINT',
'SIGTERM',
'SIGUSR1',
'SIGUSR2'
]
closeListeners.forEach(event => {
process.on(event, async () => {
await fastify.close()
process.exit(0)
})
})
九、错误响应格式 #
9.1 标准格式 #
javascript
{
"success": false,
"statusCode": 400,
"error": "ValidationError",
"message": "Email is required",
"requestId": "req-123",
"timestamp": "2024-01-01T00:00:00.000Z"
}
9.2 详细格式 #
javascript
{
"success": false,
"statusCode": 400,
"error": "ValidationError",
"message": "Validation failed",
"details": [
{
"field": "email",
"message": "must be a valid email"
},
{
"field": "password",
"message": "must be at least 8 characters"
}
],
"requestId": "req-123",
"timestamp": "2024-01-01T00:00:00.000Z"
}
十、最佳实践 #
10.1 错误分类 #
javascript
const ErrorTypes = {
VALIDATION: 'ValidationError',
AUTHENTICATION: 'AuthenticationError',
AUTHORIZATION: 'AuthorizationError',
NOT_FOUND: 'NotFoundError',
CONFLICT: 'ConflictError',
INTERNAL: 'InternalError'
}
class AppError extends Error {
constructor(type, message, statusCode = 500) {
super(message)
this.type = type
this.statusCode = statusCode
}
}
10.2 错误工厂 #
javascript
const Errors = {
notFound: (resource) => new AppError(
ErrorTypes.NOT_FOUND,
`${resource} not found`,
404
),
validation: (message) => new AppError(
ErrorTypes.VALIDATION,
message,
400
),
unauthorized: (message = 'Unauthorized') => new AppError(
ErrorTypes.AUTHENTICATION,
message,
401
),
forbidden: (message = 'Forbidden') => new AppError(
ErrorTypes.AUTHORIZATION,
message,
403
),
conflict: (message) => new AppError(
ErrorTypes.CONFLICT,
message,
409
)
}
fastify.get('/users/:id', async (request, reply) => {
const user = await findUser(request.params.id)
if (!user) {
throw Errors.notFound('User')
}
return user
})
10.3 错误日志级别 #
javascript
fastify.setErrorHandler((error, request, reply) => {
const statusCode = error.statusCode || 500
if (statusCode >= 500) {
fastify.log.error(error)
} else if (statusCode >= 400) {
fastify.log.warn(error)
}
reply.code(statusCode).send({
statusCode,
error: error.name,
message: error.message
})
})
十一、总结 #
本章我们学习了:
- 错误类型:验证错误、认证错误、权限错误等
- 基本错误处理:抛出错误、状态码错误
- 自定义错误处理器:setErrorHandler使用
- 自定义错误类:AppError、NotFoundError等
- 错误钩子:onError钩子使用
- 路由级错误处理:路由错误处理器
- 验证错误处理:Schema验证错误
- 异步错误处理:未捕获异常处理
- 错误响应格式:标准格式、详细格式
- 最佳实践:错误分类、错误工厂、日志级别
接下来让我们学习常用中间件!
最后更新:2026-03-28