错误处理模式Skill error-handling-patterns

这个技能专注于实现健壮的错误处理策略,包括创建自定义错误类、设置错误边界、设计API错误响应格式、实施重试逻辑和错误监控。它帮助开发者构建故障容忍系统,提高软件可靠性和用户体验,适用于软件开发中的错误管理。关键词:错误处理、自定义错误、错误边界、重试策略、错误监控、故障容忍、优雅降级。

架构设计 0 次安装 0 次浏览 更新于 3/18/2026

name: 错误处理模式 description: 设计健壮的错误处理策略,包括try-catch块、自定义错误类、错误边界、优雅降级和全面日志记录。用于实现异常处理、创建自定义错误类型、在React中设置错误边界、为API设计错误响应格式、实施重试逻辑、处理异步错误和Promise拒绝、记录错误以供监控、创建用户友好的错误消息,或构建故障容忍的系统以优雅地失败。

错误处理模式 - 健壮错误管理

何时使用此技能

  • 实现try-catch块进行错误处理
  • 创建具有特定错误代码的自定义错误类
  • 设置React错误边界以恢复UI错误
  • 设计一致的API错误响应格式
  • 处理异步错误和Promise拒绝
  • 实施具有指数退避的重试逻辑
  • 记录错误以供监控和调试
  • 创建用户友好的错误消息
  • 为失败的服务构建优雅降级
  • 一致地处理验证错误
  • 使用Sentry或类似工具实施错误跟踪
  • 设计故障容忍系统

何时使用此技能

  • 实现错误处理、设计故障容忍系统、调试生产问题或改进错误消息。
  • 当处理相关任务或功能时
  • 在需要此专业知识的开发过程中

使用时机:实现错误处理、设计故障容忍系统、调试生产问题或改进错误消息。

核心原则

  1. 快速失败,大声失败 - 尽早检测错误,使其可见
  2. 切勿吞没错误 - 始终记录或适当处理
  3. 可操作的错误消息 - 告诉用户出了什么问题以及如何修复
  4. 类型安全的错误 - 使用自定义错误类
  5. 优雅降级 - 系统在错误情况下仍保持功能

错误类型

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 = 'Authentication required') {
    super(message, 401, 'UNAUTHORIZED');
  }
}

export class ForbiddenError extends AppError {
  constructor(message: string = 'Insufficient permissions') {
    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(`Rate limit exceeded. Retry after ${retryAfter} seconds`, 429, 'RATE_LIMIT');
  }
}

// 用法
throw new ValidationError('Invalid email format', {
  email: 'Must be valid email address'
});

throw new NotFoundError('User', userId);

throw new ConflictError('Email already exists');

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('Division by zero')
    };
  }
  
  return {
    success: true,
    data: a / b
  };
}

// 用法
const result = divide(10, 2);

if (result.success) {
  console.log(result.data); // TypeScript知道数据存在
} else {
  console.error(result.error); // TypeScript知道错误存在
}

// ✅ 异步版本
async function fetchUser(id: string): Promise<Result<User>> {
  try {
    const user = await db.users.findById(id);
    if (!user) {
      return {
        success: false,
        error: new NotFoundError('User', id)
      };
    }
    return { success: true, data: user };
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error : new Error('Unknown 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: 'Resource already exists'
        }
      });
    }
  }

  // 未知错误默认为500
  res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message: process.env.NODE_ENV === 'production'
        ? 'An unexpected error occurred'
        : 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('User', 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('User', 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 boundary caught:', 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>Something went wrong</h1>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            Try again
          </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(`Failed to fetch user: ${response.statusText}`);
        }
        
        const data = await response.json();
        setUser(data);
      } catch (err) {
        setError(err instanceof Error ? err : new Error('Unknown 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('Unknown error');
      
      // 最后一次尝试不重试
      if (attempt === maxRetries) {
        break;
      }
      
      // 计算延迟与指数退避
      const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
      
      console.log(`Attempt ${attempt + 1} failed, retrying in ${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('Circuit breaker is OPEN');
      }
      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('Circuit breaker opened due to failures');
    }
  }
}

// 用法
const breaker = new CircuitBreaker(5, 60000);

try {
  const data = await breaker.execute(() => fetch('/api/unstable-service'));
} catch (error) {
  console.error('Service unavailable');
}

错误监控

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('Payment failed', {
  error: error.message,
  stack: error.stack,
  userId: user.id,
  orderId: order.id,
  amount: order.total
});

// 创建具有默认上下文的子记录器
const paymentLogger = logger.child({ service: 'payment' });
paymentLogger.error('Transaction failed', { transactionId: tx.id });

错误处理检查清单

错误检测:
□ 所有异步函数包装在try/catch中
□ 未处理的Promise拒绝被捕获
□ 入口点进行输入验证
□ 数据库错误处理
□ 网络错误处理

错误类型:
□ 为不同场景创建自定义错误类
□ 区分操作错误与程序员错误
□ HTTP状态码适当
□ API错误代码一致

错误消息:
□ 用户友好消息(无技术术语)
□ 可操作(告诉用户该怎么做)
□ 不暴露敏感数据
□ 对开发调试有帮助
□ 生产中通用

错误恢复:
□ 为临时故障实施重试逻辑
□ 为外部服务使用断路器
□ 优雅降级策略
□ 适当提供备用值
□ 在finally块中清理

监控:
□ 配置错误跟踪服务(如Sentry)
□ 实施结构化日志记录
□ 监控错误率
□ 关键错误警报
□ 错误报告中包含上下文

资源


记住:好的错误处理使调试更容易、用户更满意、系统更可靠。优雅地失败,彻底记录,始终提供可操作的反馈。