name: 错误处理模式 description: 设计健壮的错误处理策略,包括try-catch块、自定义错误类、错误边界、优雅降级和全面的日志记录。用于实现异常处理、创建自定义错误类型、在React中设置错误边界、为API设计错误响应格式、实现重试逻辑、处理异步错误和承诺拒绝、记录错误以进行监控、创建用户友好的错误消息,或构建能够优雅失败的容错系统。
错误处理模式 - 健壮的错误管理
何时使用此技能
- 实现try-catch块进行错误处理
- 创建具有特定错误代码的自定义错误类
- 设置React错误边界以进行UI错误恢复
- 设计一致的API错误响应格式
- 处理异步错误和承诺拒绝
- 实现具有指数退避的重试逻辑
- 记录错误以进行监控和调试
- 创建用户友好的错误消息
- 构建失败服务的优雅降级
- 一致地处理验证错误
- 使用Sentry或类似工具实现错误跟踪
- 设计容错系统
何时使用此技能
- 实现错误处理、设计容错系统、调试生产问题或改进错误消息。
- 当处理相关任务或功能时
- 在需要此专业知识的开发过程中
使用时机:实现错误处理、设计容错系统、调试生产问题或改进错误消息。
核心原则
- 快速失败,明确失败 - 早期检测错误,使其可见
- 从不吞没错误 - 总是记录或适当处理
- 可操作的错误消息 - 告诉用户出了什么问题以及如何修复
- 类型安全的错误 - 使用自定义错误类
- 优雅降级 - 系统在错误情况下保持功能性
错误类型
1. 自定义错误类
// ✅ 基础错误类
export class AppError extends Error {
constructor(
message: string,
public statusCode: number = 500,
public code: string = 'INTERNAL_ERROR',
public isOperational: boolean = true
) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
// ✅ 特定错误类型
export class ValidationError extends AppError {
constructor(message: string, public fields?: Record<string, string>) {
super(message, 400, 'VALIDATION_ERROR');
}
}
export class NotFoundError extends AppError {
constructor(resource: string, id: string) {
super(`${resource} with id ${id} not found`, 404, 'NOT_FOUND');
}
}
export class UnauthorizedError extends AppError {
constructor(message: string = '需要认证') {
super(message, 401, 'UNAUTHORIZED');
}
}
export class ForbiddenError extends AppError {
constructor(message: string = '权限不足') {
super(message, 403, 'FORBIDDEN');
}
}
export class ConflictError extends AppError {
constructor(message: string) {
super(message, 409, 'CONFLICT');
}
}
export class RateLimitError extends AppError {
constructor(public retryAfter: number) {
super(`超出速率限制。请在 ${retryAfter} 秒后重试`, 429, 'RATE_LIMIT');
}
}
// 使用示例
throw new ValidationError('无效的邮箱格式', {
email: '必须是有效的邮箱地址'
});
throw new NotFoundError('用户', userId);
throw new ConflictError('邮箱已存在');
2. 结果类型模式
// ✅ 类型安全的错误处理(无异常)
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
function divide(a: number, b: number): Result<number> {
if (b === 0) {
return {
success: false,
error: new Error('除以零')
};
}
return {
success: true,
data: a / b
};
}
// 使用示例
const result = divide(10, 2);
if (result.success) {
console.log(result.data); // TypeScript知道data存在
} else {
console.error(result.error); // TypeScript知道error存在
}
// ✅ 异步版本
async function fetchUser(id: string): Promise<Result<User>> {
try {
const user = await db.users.findById(id);
if (!user) {
return {
success: false,
error: new NotFoundError('用户', id)
};
}
return { success: true, data: user };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error('未知错误')
};
}
}
Express错误处理
1. 集中式错误处理器
// ✅ 全局错误中间件
import { Request, Response, NextFunction } from 'express';
export function errorHandler(
err: Error,
req: Request,
res: Response,
next: NextFunction
) {
// 记录错误
console.error({
error: err.message,
stack: err.stack,
url: req.url,
method: req.method,
body: req.body,
user: req.user?.id
});
// 发送到错误跟踪服务
if (process.env.NODE_ENV === 'production') {
Sentry.captureException(err);
}
// 处理自定义应用错误
if (err instanceof AppError) {
return res.status(err.statusCode).json({
error: {
code: err.code,
message: err.message,
...(err instanceof ValidationError && { fields: err.fields })
}
});
}
// 处理数据库错误
if (err.name === 'PrismaClientKnownRequestError') {
if (err.code === 'P2002') {
return res.status(409).json({
error: {
code: 'DUPLICATE_ENTRY',
message: '资源已存在'
}
});
}
}
// 默认为500处理未知错误
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: process.env.NODE_ENV === 'production'
? '发生意外错误'
: err.message
}
});
}
// 应用到Express应用
app.use(errorHandler);
2. 异步错误包装器
// ✅ 包装器以捕获异步错误
function asyncHandler(fn: Function) {
return (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
// 使用示例 - 无需try/catch!
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await userService.getUser(req.params.id);
if (!user) {
throw new NotFoundError('用户', req.params.id);
}
res.json(user);
}));
// 或使用express-async-errors包
import 'express-async-errors';
// 现在异步错误自动捕获
app.get('/users/:id', async (req, res) => {
const user = await userService.getUser(req.params.id);
if (!user) {
throw new NotFoundError('用户', req.params.id);
}
res.json(user);
});
React错误处理
1. 错误边界
// ✅ 错误边界组件
import React, { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: (error: Error) => ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
export class ErrorBoundary extends Component<Props, State> {
state: State = {
hasError: false,
error: null
};
static getDerivedStateFromError(error: Error): State {
return {
hasError: true,
error
};
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('错误边界捕获:', error, errorInfo);
// 发送到错误跟踪
Sentry.captureException(error, {
contexts: {
react: {
componentStack: errorInfo.componentStack
}
}
});
}
render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback(this.state.error!);
}
return (
<div className="error-container">
<h1>出了点问题</h1>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false, error: null })}>
重试
</button>
</div>
);
}
return this.props.children;
}
}
// 使用示例
function App() {
return (
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
<MyComponent />
</ErrorBoundary>
);
}
2. 异步错误处理
// ✅ 在组件中处理异步错误
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [error, setError] = useState<Error | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUser() {
try {
setLoading(true);
setError(null);
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`获取用户失败:${response.statusText}`);
}
const data = await response.json();
setUser(data);
} catch (err) {
setError(err instanceof Error ? err : new Error('未知错误'));
} finally {
setLoading(false);
}
}
fetchUser();
}, [userId]);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!user) return <NotFound />;
return <div>{user.name}</div>;
}
// ✅ 或使用React Query进行更好的错误处理
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)
});
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!user) return <NotFound />;
return <div>{user.name}</div>;
}
重试策略
1. 指数退避
// ✅ 使用指数退避重试
async function fetchWithRetry<T>(
fn: () => Promise<T>,
options: {
maxRetries?: number;
baseDelay?: number;
maxDelay?: number;
} = {}
): Promise<T> {
const {
maxRetries = 3,
baseDelay = 1000,
maxDelay = 30000
} = options;
let lastError: Error;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error instanceof Error ? error : new Error('未知错误');
// 最后一次尝试不重试
if (attempt === maxRetries) {
break;
}
// 计算具有指数退避的延迟
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
console.log(`尝试 ${attempt + 1} 失败,${delay}ms后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError!;
}
// 使用示例
const user = await fetchWithRetry(() => fetch('/api/user').then(r => r.json()), {
maxRetries: 3,
baseDelay: 1000
});
2. 断路器模式
// ✅ 断路器模式
class CircuitBreaker {
private failureCount = 0;
private successCount = 0;
private nextAttempt = Date.now();
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
constructor(
private threshold: number = 5,
private timeout: number = 60000,
private monitoringPeriod: number = 120000
) {}
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('断路器已打开');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess() {
this.failureCount = 0;
if (this.state === 'HALF_OPEN') {
this.state = 'CLOSED';
this.successCount = 0;
}
}
private onFailure() {
this.failureCount++;
this.successCount = 0;
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
console.error('由于失败过多,断路器打开');
}
}
}
// 使用示例
const breaker = new CircuitBreaker(5, 60000);
try {
const data = await breaker.execute(() => fetch('/api/unstable-service'));
} catch (error) {
console.error('服务不可用');
}
错误监控
1. Sentry集成
// ✅ Sentry设置
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
beforeSend(event, hint) {
// 过滤敏感数据
if (event.request) {
delete event.request.cookies;
delete event.request.headers?.authorization;
}
return event;
}
});
// 为错误添加上下文
Sentry.setUser({
id: user.id,
email: user.email
});
Sentry.setContext('order', {
id: order.id,
total: order.total
});
// 捕获带有上下文的异常
try {
await processPayment(order);
} catch (error) {
Sentry.captureException(error, {
tags: {
section: 'payment',
paymentMethod: order.paymentMethod
},
extra: {
orderId: order.id,
amount: order.total
}
});
throw error;
}
2. 结构化日志记录
// ✅ 带有错误跟踪的Winston日志记录器
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// 记录带有上下文的信息
logger.error('支付失败', {
error: error.message,
stack: error.stack,
userId: user.id,
orderId: order.id,
amount: order.total
});
// 创建具有默认上下文的子日志记录器
const paymentLogger = logger.child({ service: 'payment' });
paymentLogger.error('交易失败', { transactionId: tx.id });
错误处理检查清单
错误检测:
□ 所有异步函数包装在try/catch中
□ 未处理的承诺拒绝被捕获
□ 输入点在入口处验证
□ 数据库错误已处理
□ 网络错误已处理
错误类型:
□ 为不同场景使用自定义错误类
□ 区分操作性和程序员错误
□ HTTP状态码适当
□ API中错误代码一致
错误消息:
□ 用户友好的消息(无技术术语)
□ 可操作(告诉用户该做什么)
□ 不暴露敏感数据
□ 对开发调试有帮助
□ 生产环境中通用
错误恢复:
□ 为临时失败实现重试逻辑
□ 为外部服务使用断路器
□ 优雅降级策略
□ 在适当时使用回退值
□ 在finally块中进行清理
监控:
□ 配置错误跟踪服务(Sentry等)
□ 实现结构化日志记录
□ 监控错误率
□ 关键错误警报
□ 错误报告中包含上下文
资源
记住:良好的错误处理使调试更容易、用户更满意、系统更可靠。优雅失败、全面记录,并始终提供可操作的反馈。