优雅的 async/await 错误处理模式指南

优雅的 async/await 错误处理模式指南

告别手动 try-catch,拥抱更优雅的异步错误处理方案

🎯 背景

在现代前端开发中,async/await 已经成为处理异步操作的主流方式。然而,传统的错误处理需要大量的 try-catch 块,代码冗余且不够优雅。本文将介绍几种更优雅的错误处理模式,让你的代码更加简洁和可维护。

😕 传统的 try-catch 问题

常见的冗余代码

javascript 复制代码
// 传统方式 - 每个 async 函数都需要 try-catch
async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('获取用户数据失败:', error);
    throw error;
  }
}

async function updateUserProfile(userId, profileData) {
  try {
    const response = await fetch(`/api/users/${userId}`, {
      method: 'PUT',
      body: JSON.stringify(profileData),
    });
    return await response.json();
  } catch (error) {
    console.error('更新用户资料失败:', error);
    throw error;
  }
}

// 使用时还需要再次 try-catch
async function handleUserUpdate() {
  try {
    const user = await fetchUserData(123);
    const result = await updateUserProfile(123, { name: '新名字' });
    console.log('更新成功', result);
  } catch (error) {
    // 错误处理
    showErrorMessage(error.message);
  }
}

问题分析

  1. 代码重复:每个异步函数都需要类似的错误处理
  2. 嵌套复杂:多层 try-catch 导致代码难以阅读
  3. 维护困难:错误处理逻辑分散,难以统一管理
  4. 忘记处理:容易遗漏某些异步操作的错误处理

🚀 优雅的错误处理方案

1. to() 函数模式 - Go 语言风格

这是一种非常流行的错误处理模式,灵感来自 Go 语言的错误处理方式。

javascript 复制代码
/**
 * 将 Promise 转换为 [error, data] 元组
 * @param {Promise} promise - 要处理的 Promise
 * @returns {Promise<[Error|null, any]>} 返回 [错误, 数据] 的元组
 */
function to(promise) {
  return promise.then((data) => [null, data]).catch((error) => [error, null]);
}

// 使用示例
async function fetchUserData(userId) {
  const [error, data] = await to(
    fetch(`/api/users/${userId}`).then((res) => res.json())
  );

  if (error) {
    console.error('获取用户数据失败:', error);
    return null;
  }

  return data;
}

async function handleUserOperation() {
  const [userError, user] = await to(fetchUserData(123));
  if (userError) {
    showErrorMessage('无法获取用户信息');
    return;
  }

  const [updateError, result] = await to(
    updateUserProfile(123, { name: '新名字' })
  );

  if (updateError) {
    showErrorMessage('更新失败,请重试');
    return;
  }

  console.log('操作成功', result);
}

2. 增强版 to() 函数

支持类型安全和更多功能的版本:

typescript 复制代码
/**
 * 增强版错误处理函数
 * @param promise - 要处理的 Promise
 * @param errorExt - 可选的错误扩展对象
 */
function to<T, U = Error>(
  promise: Promise<T>,
  errorExt?: object
): Promise<[U, undefined] | [null, T]> {
  return promise
    .then<[null, T]>((data: T) => [null, data])
    .catch<[U, undefined]>((err: U) => {
      if (errorExt) {
        const parsedError = Object.assign(err, errorExt);
        return [parsedError, undefined];
      }
      return [err, undefined];
    });
}

// TypeScript 使用示例
interface User {
  id: number;
  name: string;
  email: string;
}

async function fetchUser(id: number): Promise<User | null> {
  const [error, user] = await to<User>(
    fetch(`/api/users/${id}`).then((res) => res.json()),
    { operation: 'fetchUser', userId: id }
  );

  if (error) {
    console.error('获取用户失败:', error);
    return null;
  }

  return user;
}

3. 错误处理装饰器模式

使用装饰器来自动处理错误:

javascript 复制代码
/**
 * 错误处理装饰器
 * @param {Function} errorHandler - 错误处理函数
 */
function withErrorHandling(errorHandler) {
  return function (target, propertyKey, descriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args) {
      try {
        return await originalMethod.apply(this, args);
      } catch (error) {
        return errorHandler(error, propertyKey, args);
      }
    };

    return descriptor;
  };
}

// 使用示例
class UserService {
  @withErrorHandling((error, method, args) => {
    console.error(`${method} 方法执行失败:`, error);
    return null;
  })
  async fetchUser(id) {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
  }

  @withErrorHandling((error, method, args) => {
    console.error(`${method} 方法执行失败:`, error);
    throw new Error('用户操作失败,请重试');
  })
  async updateUser(id, data) {
    const response = await fetch(`/api/users/${id}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    });
    return response.json();
  }
}

4. 链式错误处理模式

创建一个支持链式调用的错误处理类:

javascript 复制代码
class AsyncChain {
  constructor(promise = Promise.resolve()) {
    this.promise = promise;
    this.errors = [];
  }

  static of(promise) {
    return new AsyncChain(promise);
  }

  then(fn) {
    this.promise = this.promise.then(async (data) => {
      if (this.errors.length > 0) return data;

      try {
        return await fn(data);
      } catch (error) {
        this.errors.push(error);
        return data;
      }
    });
    return this;
  }

  catch(errorHandler) {
    this.promise = this.promise.then((data) => {
      if (this.errors.length > 0) {
        errorHandler(this.errors);
        this.errors = [];
      }
      return data;
    });
    return this;
  }

  finally(finallyHandler) {
    this.promise = this.promise.finally(finallyHandler);
    return this;
  }

  execute() {
    return this.promise;
  }
}

// 使用示例
async function handleComplexOperation() {
  const result = await AsyncChain.of(Promise.resolve({ userId: 123 }))
    .then(async (data) => {
      const user = await fetch(`/api/users/${data.userId}`).then((r) =>
        r.json()
      );
      return { ...data, user };
    })
    .then(async (data) => {
      const profile = await fetch(`/api/profiles/${data.user.id}`).then((r) =>
        r.json()
      );
      return { ...data, profile };
    })
    .then(async (data) => {
      await fetch('/api/analytics', {
        method: 'POST',
        body: JSON.stringify({ event: 'user_viewed', userId: data.user.id }),
      });
      return data;
    })
    .catch((errors) => {
      console.error('操作中出现错误:', errors);
      showErrorMessage('操作失败,请重试');
    })
    .execute();

  return result;
}

5. 结果包装器模式

创建一个统一的结果包装器:

javascript 复制代码
class Result {
  constructor(success, data, error) {
    this.success = success;
    this.data = data;
    this.error = error;
  }

  static ok(data) {
    return new Result(true, data, null);
  }

  static error(error) {
    return new Result(false, null, error);
  }

  static async from(promise) {
    try {
      const data = await promise;
      return Result.ok(data);
    } catch (error) {
      return Result.error(error);
    }
  }

  isOk() {
    return this.success;
  }

  isError() {
    return !this.success;
  }

  unwrap() {
    if (this.success) {
      return this.data;
    }
    throw this.error;
  }

  unwrapOr(defaultValue) {
    return this.success ? this.data : defaultValue;
  }

  map(fn) {
    if (this.success) {
      try {
        return Result.ok(fn(this.data));
      } catch (error) {
        return Result.error(error);
      }
    }
    return this;
  }

  mapError(fn) {
    if (this.isError()) {
      return Result.error(fn(this.error));
    }
    return this;
  }
}

// 使用示例
async function fetchUserProfile(userId) {
  const userResult = await Result.from(
    fetch(`/api/users/${userId}`).then((r) => r.json())
  );

  if (userResult.isError()) {
    return userResult.mapError(
      (error) => new Error(`获取用户失败: ${error.message}`)
    );
  }

  const profileResult = await Result.from(
    fetch(`/api/profiles/${userId}`).then((r) => r.json())
  );

  return profileResult.map((profile) => ({
    user: userResult.data,
    profile,
  }));
}

// 链式操作
async function handleUserData(userId) {
  const result = await fetchUserProfile(userId);

  const finalData = result
    .map((data) => ({
      ...data,
      displayName: data.user.name || data.profile.nickname,
    }))
    .map((data) => ({
      ...data,
      isVip: data.profile.level > 5,
    }))
    .unwrapOr({ displayName: '未知用户', isVip: false });

  console.log('处理结果:', finalData);
}

🛠️ 实用工具函数库

创建一个完整的错误处理工具库:

javascript 复制代码
// errorUtils.js
export class ErrorUtils {
  /**
   * Go 风格的错误处理
   */
  static to(promise) {
    return promise.then((data) => [null, data]).catch((error) => [error, null]);
  }

  /**
   * 带重试的异步操作
   */
  static async retry(fn, maxAttempts = 3, delay = 1000) {
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      const [error, result] = await ErrorUtils.to(fn());

      if (!error) {
        return [null, result];
      }

      if (attempt === maxAttempts) {
        return [error, null];
      }

      await new Promise((resolve) => setTimeout(resolve, delay * attempt));
    }
  }

  /**
   * 超时处理
   */
  static withTimeout(promise, timeout = 5000) {
    const timeoutPromise = new Promise((_, reject) => {
      setTimeout(() => reject(new Error('操作超时')), timeout);
    });

    return Promise.race([promise, timeoutPromise]);
  }

  /**
   * 条件执行
   */
  static async when(condition, asyncFn) {
    if (typeof condition === 'function') {
      condition = await condition();
    }

    if (condition) {
      return await asyncFn();
    }

    return null;
  }

  /**
   * 安全的 JSON 解析
   */
  static safeJsonParse(str, defaultValue = null) {
    try {
      return JSON.parse(str);
    } catch {
      return defaultValue;
    }
  }

  /**
   * 批量处理异步操作
   */
  static async parallel(
    operations,
    { maxConcurrency = 5, failFast = false } = {}
  ) {
    const results = [];
    const errors = [];

    for (let i = 0; i < operations.length; i += maxConcurrency) {
      const batch = operations.slice(i, i + maxConcurrency);
      const batchPromises = batch.map(async (op, index) => {
        const [error, result] = await ErrorUtils.to(op());

        if (error) {
          errors.push({ index: i + index, error });
          if (failFast) {
            throw error;
          }
          return null;
        }

        return result;
      });

      const batchResults = await Promise.all(batchPromises);
      results.push(...batchResults);
    }

    return { results, errors };
  }
}

// 使用示例
async function complexDataProcessing() {
  // 1. 带重试的数据获取
  const [fetchError, userData] = await ErrorUtils.retry(
    () => fetch('/api/user').then((r) => r.json()),
    3,
    1000
  );

  if (fetchError) {
    console.error('获取用户数据失败:', fetchError);
    return;
  }

  // 2. 并行处理多个操作
  const operations = userData.items.map((item) => () => processItem(item.id));

  const { results, errors } = await ErrorUtils.parallel(operations, {
    maxConcurrency: 3,
    failFast: false,
  });

  if (errors.length > 0) {
    console.warn('部分操作失败:', errors);
  }

  console.log('处理完成:', results.filter(Boolean));
}

🎨 在 Vue/React 中的应用

Vue 3 Composition API

javascript 复制代码
// composables/useAsyncData.js
import { ref, unref } from 'vue';
import { ErrorUtils } from './errorUtils';

export function useAsyncData(asyncFn, options = {}) {
  const loading = ref(false);
  const data = ref(null);
  const error = ref(null);

  const {
    immediate = true,
    resetOnExecute = true,
    onError,
    onSuccess,
  } = options;

  const execute = async (...args) => {
    if (resetOnExecute) {
      data.value = null;
      error.value = null;
    }

    loading.value = true;

    const [err, result] = await ErrorUtils.to(asyncFn(...args.map(unref)));

    loading.value = false;

    if (err) {
      error.value = err;
      onError?.(err);
    } else {
      data.value = result;
      onSuccess?.(result);
    }

    return [err, result];
  };

  if (immediate) {
    execute();
  }

  return {
    loading: readonly(loading),
    data: readonly(data),
    error: readonly(error),
    execute,
  };
}

// 在组件中使用
export default {
  setup() {
    const { loading, data, error, execute } = useAsyncData(
      (userId) => fetch(`/api/users/${userId}`).then((r) => r.json()),
      {
        immediate: false,
        onError: (err) => console.error('请求失败:', err),
        onSuccess: (data) => console.log('请求成功:', data),
      }
    );

    const loadUser = (id) => execute(id);

    return {
      loading,
      data,
      error,
      loadUser,
    };
  },
};

React Hook

javascript 复制代码
// hooks/useAsyncOperation.js
import { useState, useCallback } from 'react';
import { ErrorUtils } from '../utils/errorUtils';

export function useAsyncOperation(asyncFn, options = {}) {
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  const { onError, onSuccess, resetOnExecute = true } = options;

  const execute = useCallback(
    async (...args) => {
      if (resetOnExecute) {
        setData(null);
        setError(null);
      }

      setLoading(true);

      const [err, result] = await ErrorUtils.to(asyncFn(...args));

      setLoading(false);

      if (err) {
        setError(err);
        onError?.(err);
      } else {
        setData(result);
        onSuccess?.(result);
      }

      return [err, result];
    },
    [asyncFn, onError, onSuccess, resetOnExecute]
  );

  return {
    loading,
    data,
    error,
    execute,
  };
}

// 在组件中使用
function UserProfile({ userId }) {
  const { loading, data, error, execute } = useAsyncOperation(
    (id) => fetch(`/api/users/${id}`).then((r) => r.json()),
    {
      onError: (err) => toast.error('加载用户信息失败'),
      onSuccess: (data) => console.log('用户信息加载成功', data),
    }
  );

  useEffect(() => {
    if (userId) {
      execute(userId);
    }
  }, [userId, execute]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!data) return null;

  return <div>{/* 渲染用户信息 */}</div>;
}

📈 最佳实践

1. 选择合适的模式

  • 简单场景 :使用 to() 函数
  • 复杂逻辑:使用 Result 包装器
  • 重复操作:使用装饰器模式
  • 链式操作:使用 AsyncChain

2. 错误分类处理

javascript 复制代码
class AppError extends Error {
  constructor(message, code, statusCode = 500) {
    super(message);
    this.code = code;
    this.statusCode = statusCode;
    this.name = 'AppError';
  }
}

class ValidationError extends AppError {
  constructor(message, field) {
    super(message, 'VALIDATION_ERROR', 400);
    this.field = field;
  }
}

class NetworkError extends AppError {
  constructor(message) {
    super(message, 'NETWORK_ERROR', 0);
  }
}

// 错误处理中心
class ErrorHandler {
  static handle(error) {
    if (error instanceof ValidationError) {
      showFieldError(error.field, error.message);
    } else if (error instanceof NetworkError) {
      showNetworkError();
    } else if (error instanceof AppError) {
      showError(error.message);
    } else {
      console.error('未知错误:', error);
      showError('系统错误,请稍后重试');
    }
  }
}

3. 统一的 API 错误处理

javascript 复制代码
// api.js
class ApiClient {
  constructor(baseURL) {
    this.baseURL = baseURL;
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;

    const [error, response] = await ErrorUtils.to(
      ErrorUtils.withTimeout(
        fetch(url, {
          headers: {
            'Content-Type': 'application/json',
            ...options.headers,
          },
          ...options,
        }),
        10000
      )
    );

    if (error) {
      throw new NetworkError('网络请求失败');
    }

    if (!response.ok) {
      const errorData = await response.json().catch(() => ({}));

      switch (response.status) {
        case 400:
          throw new ValidationError(errorData.message || '请求参数错误');
        case 401:
          throw new AppError('未授权访问', 'UNAUTHORIZED', 401);
        case 403:
          throw new AppError('权限不足', 'FORBIDDEN', 403);
        case 404:
          throw new AppError('资源不存在', 'NOT_FOUND', 404);
        default:
          throw new AppError('服务器错误', 'SERVER_ERROR', response.status);
      }
    }

    return response.json();
  }

  get(endpoint, options) {
    return this.request(endpoint, { method: 'GET', ...options });
  }

  post(endpoint, data, options) {
    return this.request(endpoint, {
      method: 'POST',
      body: JSON.stringify(data),
      ...options,
    });
  }
}

const api = new ApiClient('/api');

// 使用示例
async function createUser(userData) {
  const [error, user] = await ErrorUtils.to(api.post('/users', userData));

  if (error) {
    ErrorHandler.handle(error);
    return null;
  }

  return user;
}

🔧 工具推荐

NPM 包

  1. await-to-js - 最受欢迎的 to() 函数实现
  2. neverthrow - Result 类型的完整实现
  3. p-retry - 专业的重试工具
  4. p-timeout - Promise 超时处理

IDE 插件

  1. Error Lens - VS Code 错误高亮显示
  2. ESLint - 配置 async/await 相关规则

📚 总结

优雅的 async/await 错误处理不仅能让代码更简洁,还能提高代码的可维护性和可读性。选择合适的模式,结合项目的实际需求,可以大大提升开发效率。

推荐方案选择

  • 新项目:优先使用 Result 包装器模式
  • 现有项目改造:使用 to() 函数渐进式改进
  • 团队协作:统一使用工具函数库
  • 复杂应用:结合多种模式,分场景使用

记住,最好的错误处理方案是团队都能理解和维护的方案。选择适合你项目的模式,保持一致性,让代码更加优雅!

🌟 更多实际应用场景

1. 电商购物车场景

javascript 复制代码
// 购物车操作的复杂错误处理
class ShoppingCartService {
  constructor(apiClient) {
    this.api = apiClient;
  }

  // 添加商品到购物车
  async addToCart(productId, quantity = 1) {
    // 1. 验证商品库存
    const [stockError, stockInfo] = await ErrorUtils.to(
      this.api.get(`/products/${productId}/stock`)
    );

    if (stockError) {
      throw new AppError('无法获取商品库存信息', 'STOCK_CHECK_FAILED');
    }

    if (stockInfo.available < quantity) {
      throw new ValidationError('库存不足', 'quantity');
    }

    // 2. 检查用户购物车容量
    const [cartError, currentCart] = await ErrorUtils.to(this.api.get('/cart'));

    if (cartError) {
      throw new AppError('无法获取购物车信息', 'CART_FETCH_FAILED');
    }

    if (currentCart.items.length >= 50) {
      throw new ValidationError('购物车商品数量已达上限', 'cart_limit');
    }

    // 3. 添加商品
    const [addError, result] = await ErrorUtils.to(
      this.api.post('/cart/items', {
        productId,
        quantity,
      })
    );

    if (addError) {
      if (addError.code === 'PRODUCT_UNAVAILABLE') {
        throw new AppError('商品已下架', 'PRODUCT_UNAVAILABLE');
      }
      throw new AppError('添加失败,请重试', 'ADD_TO_CART_FAILED');
    }

    return result;
  }

  // 批量更新购物车
  async batchUpdateCart(updates) {
    const results = [];
    const failures = [];

    for (const update of updates) {
      const [error, result] = await ErrorUtils.to(
        this.updateCartItem(update.itemId, update.quantity)
      );

      if (error) {
        failures.push({
          itemId: update.itemId,
          error: error.message,
        });
      } else {
        results.push(result);
      }
    }

    return {
      success: results,
      failures,
      hasFailures: failures.length > 0,
    };
  }

  async updateCartItem(itemId, quantity) {
    if (quantity <= 0) {
      return this.api.delete(`/cart/items/${itemId}`);
    }

    return this.api.put(`/cart/items/${itemId}`, { quantity });
  }
}

// 使用示例
const cartService = new ShoppingCartService(api);

async function handleAddToCart(productId, quantity) {
  const [error, result] = await ErrorUtils.to(
    cartService.addToCart(productId, quantity)
  );

  if (error) {
    switch (error.code) {
      case 'STOCK_CHECK_FAILED':
        showMessage('网络异常,请稍后重试', 'error');
        break;
      case 'PRODUCT_UNAVAILABLE':
        showMessage('商品已售罄', 'warning');
        break;
      case 'ADD_TO_CART_FAILED':
        showMessage('添加失败,请重试', 'error');
        break;
      default:
        if (error instanceof ValidationError) {
          showFieldError(error.field, error.message);
        } else {
          showMessage('操作失败', 'error');
        }
    }
    return;
  }

  showMessage('添加成功', 'success');
  updateCartBadge(result.totalItems);
}

2. 文件上传进度场景

javascript 复制代码
// 文件上传的错误处理和进度跟踪
class FileUploadService {
  constructor() {
    this.activeUploads = new Map();
  }

  async uploadFile(file, options = {}) {
    const uploadId = this.generateUploadId();
    const {
      onProgress,
      onSuccess,
      onError,
      maxSize = 50 * 1024 * 1024, // 50MB
      allowedTypes = ['image/*', 'application/pdf'],
    } = options;

    // 文件验证
    const validationResult = this.validateFile(file, { maxSize, allowedTypes });
    if (!validationResult.valid) {
      const error = new ValidationError(validationResult.message, 'file');
      onError?.(error);
      return Result.error(error);
    }

    try {
      // 1. 获取上传凭证
      const [credError, credentials] = await ErrorUtils.to(
        this.getUploadCredentials(file.name, file.size, file.type)
      );

      if (credError) {
        throw new AppError('获取上传凭证失败', 'CREDENTIALS_FAILED');
      }

      // 2. 分片上传
      const chunks = this.createFileChunks(file);
      const uploadedChunks = [];

      this.activeUploads.set(uploadId, {
        file,
        chunks: chunks.length,
        uploaded: 0,
        status: 'uploading',
      });

      for (let i = 0; i < chunks.length; i++) {
        const chunk = chunks[i];

        // 重试机制
        const [chunkError, chunkResult] = await ErrorUtils.retry(
          () => this.uploadChunk(credentials.uploadUrl, chunk, i),
          3,
          1000
        );

        if (chunkError) {
          this.activeUploads.delete(uploadId);
          throw new AppError(`分片 ${i + 1} 上传失败`, 'CHUNK_UPLOAD_FAILED');
        }

        uploadedChunks.push(chunkResult);

        // 更新进度
        const progress = Math.round(((i + 1) / chunks.length) * 100);
        onProgress?.({
          progress,
          uploadId,
          chunk: i + 1,
          total: chunks.length,
        });

        this.activeUploads.get(uploadId).uploaded = i + 1;
      }

      // 3. 合并文件
      const [mergeError, finalResult] = await ErrorUtils.to(
        this.mergeChunks(credentials.fileId, uploadedChunks)
      );

      if (mergeError) {
        throw new AppError('文件合并失败', 'MERGE_FAILED');
      }

      this.activeUploads.delete(uploadId);
      onSuccess?.(finalResult);

      return Result.ok(finalResult);
    } catch (error) {
      this.activeUploads.delete(uploadId);
      onError?.(error);
      return Result.error(error);
    }
  }

  validateFile(file, { maxSize, allowedTypes }) {
    if (!file) {
      return { valid: false, message: '请选择文件' };
    }

    if (file.size > maxSize) {
      const sizeMB = Math.round(maxSize / 1024 / 1024);
      return { valid: false, message: `文件大小不能超过 ${sizeMB}MB` };
    }

    const isValidType = allowedTypes.some((type) => {
      if (type.endsWith('/*')) {
        return file.type.startsWith(type.slice(0, -1));
      }
      return file.type === type;
    });

    if (!isValidType) {
      return { valid: false, message: '文件类型不支持' };
    }

    return { valid: true };
  }

  async getUploadCredentials(fileName, fileSize, fileType) {
    return api.post('/upload/credentials', {
      fileName,
      fileSize,
      fileType,
    });
  }

  createFileChunks(file, chunkSize = 2 * 1024 * 1024) {
    // 2MB per chunk
    const chunks = [];
    let start = 0;

    while (start < file.size) {
      const end = Math.min(start + chunkSize, file.size);
      chunks.push(file.slice(start, end));
      start = end;
    }

    return chunks;
  }

  async uploadChunk(uploadUrl, chunk, index) {
    const formData = new FormData();
    formData.append('chunk', chunk);
    formData.append('index', index);

    return fetch(uploadUrl, {
      method: 'POST',
      body: formData,
    }).then((r) => r.json());
  }

  async mergeChunks(fileId, chunks) {
    return api.post(`/upload/${fileId}/merge`, {
      chunks: chunks.map((c) => c.chunkId),
    });
  }

  generateUploadId() {
    return `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  // 取消上传
  cancelUpload(uploadId) {
    if (this.activeUploads.has(uploadId)) {
      this.activeUploads.get(uploadId).status = 'cancelled';
      this.activeUploads.delete(uploadId);
    }
  }

  // 获取上传状态
  getUploadStatus(uploadId) {
    return this.activeUploads.get(uploadId) || null;
  }
}

// Vue 3 组合式函数
export function useFileUpload() {
  const uploadService = new FileUploadService();
  const uploads = ref(new Map());

  const uploadFile = async (file, options = {}) => {
    const uploadId = `upload_${Date.now()}`;

    uploads.value.set(uploadId, {
      file,
      progress: 0,
      status: 'uploading',
      error: null,
      result: null,
    });

    const result = await uploadService.uploadFile(file, {
      ...options,
      onProgress: ({ progress }) => {
        const upload = uploads.value.get(uploadId);
        if (upload) {
          upload.progress = progress;
        }
      },
      onSuccess: (result) => {
        const upload = uploads.value.get(uploadId);
        if (upload) {
          upload.status = 'completed';
          upload.result = result;
        }
      },
      onError: (error) => {
        const upload = uploads.value.get(uploadId);
        if (upload) {
          upload.status = 'error';
          upload.error = error;
        }
      },
    });

    return { uploadId, result };
  };

  const cancelUpload = (uploadId) => {
    uploadService.cancelUpload(uploadId);
    uploads.value.delete(uploadId);
  };

  return {
    uploads: readonly(uploads),
    uploadFile,
    cancelUpload,
  };
}

3. 数据同步和缓存场景

javascript 复制代码
// 复杂的数据同步和缓存错误处理
class DataSyncService {
  constructor() {
    this.cache = new Map();
    this.syncQueue = [];
    this.isSyncing = false;
    this.retryAttempts = new Map();
  }

  // 获取数据(带缓存)
  async getData(key, fetcher, options = {}) {
    const {
      cacheTime = 5 * 60 * 1000, // 5分钟缓存
      forceRefresh = false,
      fallbackData = null,
    } = options;

    // 检查缓存
    if (!forceRefresh && this.cache.has(key)) {
      const cached = this.cache.get(key);
      if (Date.now() - cached.timestamp < cacheTime) {
        return Result.ok(cached.data);
      }
    }

    // 获取新数据
    const [error, data] = await ErrorUtils.to(fetcher());

    if (error) {
      // 如果有缓存数据,返回缓存
      if (this.cache.has(key)) {
        const cached = this.cache.get(key);
        console.warn(`获取 ${key} 失败,使用缓存数据:`, error);
        return Result.ok(cached.data);
      }

      // 如果有降级数据,返回降级数据
      if (fallbackData !== null) {
        console.warn(`获取 ${key} 失败,使用降级数据:`, error);
        return Result.ok(fallbackData);
      }

      return Result.error(error);
    }

    // 更新缓存
    this.cache.set(key, {
      data,
      timestamp: Date.now(),
    });

    return Result.ok(data);
  }

  // 离线数据同步
  async syncOfflineData() {
    if (this.isSyncing) {
      return { success: false, message: '正在同步中' };
    }

    this.isSyncing = true;
    const results = {
      success: [],
      failed: [],
      skipped: [],
    };

    try {
      // 检查网络状态
      if (!navigator.onLine) {
        throw new AppError('网络不可用', 'NETWORK_UNAVAILABLE');
      }

      // 处理同步队列
      while (this.syncQueue.length > 0) {
        const item = this.syncQueue.shift();
        const retryKey = `${item.type}_${item.id}`;
        const retryCount = this.retryAttempts.get(retryKey) || 0;

        // 超过重试次数,跳过
        if (retryCount >= 3) {
          results.skipped.push({
            ...item,
            reason: '超过最大重试次数',
          });
          this.retryAttempts.delete(retryKey);
          continue;
        }

        const [error, result] = await ErrorUtils.to(this.syncSingleItem(item));

        if (error) {
          // 增加重试次数
          this.retryAttempts.set(retryKey, retryCount + 1);

          // 如果是临时性错误,重新加入队列
          if (this.isRetryableError(error)) {
            this.syncQueue.push(item);
          }

          results.failed.push({
            ...item,
            error: error.message,
            retryCount: retryCount + 1,
          });
        } else {
          results.success.push({
            ...item,
            result,
          });
          this.retryAttempts.delete(retryKey);
        }

        // 避免过快的请求
        await new Promise((resolve) => setTimeout(resolve, 100));
      }
    } finally {
      this.isSyncing = false;
    }

    return results;
  }

  async syncSingleItem(item) {
    switch (item.type) {
      case 'user_preference':
        return api.put('/user/preferences', item.data);

      case 'analytics_event':
        return api.post('/analytics/events', item.data);

      case 'draft_save':
        return api.post('/drafts', item.data);

      default:
        throw new AppError(`未知的同步类型: ${item.type}`, 'UNKNOWN_SYNC_TYPE');
    }
  }

  isRetryableError(error) {
    // 网络错误、服务器临时错误等可重试
    return (
      error instanceof NetworkError ||
      (error.statusCode >= 500 && error.statusCode < 600) ||
      error.statusCode === 429
    ); // 限流
  }

  // 添加到同步队列
  addToSyncQueue(type, id, data) {
    this.syncQueue.push({
      type,
      id,
      data,
      timestamp: Date.now(),
    });
  }

  // 清理过期缓存
  cleanExpiredCache(maxAge = 30 * 60 * 1000) {
    // 30分钟
    const now = Date.now();
    for (const [key, value] of this.cache.entries()) {
      if (now - value.timestamp > maxAge) {
        this.cache.delete(key);
      }
    }
  }
}

// 使用示例
const syncService = new DataSyncService();

// 获取用户数据(带缓存和降级)
async function fetchUserProfile(userId) {
  const result = await syncService.getData(
    `user_profile_${userId}`,
    () => api.get(`/users/${userId}`),
    {
      cacheTime: 10 * 60 * 1000, // 10分钟缓存
      fallbackData: {
        id: userId,
        name: '用户',
        avatar: '/default-avatar.png',
      },
    }
  );

  if (result.isError()) {
    console.error('获取用户资料失败:', result.error);
    return null;
  }

  return result.data;
}

// 离线操作处理
async function saveUserPreference(key, value) {
  const [error, result] = await ErrorUtils.to(
    api.put('/user/preferences', { [key]: value })
  );

  if (error) {
    // 离线时保存到同步队列
    if (!navigator.onLine || error instanceof NetworkError) {
      syncService.addToSyncQueue('user_preference', key, { [key]: value });
      console.log('已添加到离线同步队列');
      return true;
    }

    throw error;
  }

  return result;
}

4. 实时数据流处理场景

javascript 复制代码
// WebSocket 连接和实时数据的错误处理
class RealtimeDataService {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
    this.reconnectDelay = 1000;
    this.listeners = new Map();
    this.messageQueue = [];
    this.isConnecting = false;
  }

  async connect() {
    if (this.isConnecting) {
      return;
    }

    this.isConnecting = true;

    try {
      await this.createConnection();
      this.reconnectAttempts = 0;
      this.isConnecting = false;
    } catch (error) {
      this.isConnecting = false;
      throw error;
    }
  }

  async createConnection() {
    return new Promise((resolve, reject) => {
      try {
        this.ws = new WebSocket(this.url);

        this.ws.onopen = () => {
          console.log('WebSocket 连接成功');
          this.processMessageQueue();
          resolve();
        };

        this.ws.onmessage = (event) => {
          this.handleMessage(event.data);
        };

        this.ws.onclose = (event) => {
          console.log('WebSocket 连接关闭:', event.code, event.reason);
          this.handleReconnect();
        };

        this.ws.onerror = (error) => {
          console.error('WebSocket 错误:', error);
          reject(new NetworkError('WebSocket 连接失败'));
        };

        // 连接超时
        setTimeout(() => {
          if (this.ws.readyState === WebSocket.CONNECTING) {
            this.ws.close();
            reject(new NetworkError('WebSocket 连接超时'));
          }
        }, 10000);
      } catch (error) {
        reject(error);
      }
    });
  }

  async handleReconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      this.emit('maxReconnectAttemptsReached');
      return;
    }

    this.reconnectAttempts++;
    const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);

    console.log(
      `尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts}),${delay}ms 后重试`
    );

    setTimeout(async () => {
      const [error] = await ErrorUtils.to(this.connect());

      if (error) {
        console.error('重连失败:', error);
        this.handleReconnect();
      }
    }, delay);
  }

  handleMessage(data) {
    try {
      const message = JSON.parse(data);
      this.emit('message', message);

      // 根据消息类型分发
      if (message.type) {
        this.emit(message.type, message.payload);
      }
    } catch (error) {
      console.error('解析消息失败:', error, data);
      this.emit('parseError', { error, rawData: data });
    }
  }

  send(data) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
      return true;
    } else {
      // 连接未就绪时加入队列
      this.messageQueue.push(data);

      // 尝试连接
      if (!this.isConnecting) {
        this.connect().catch((error) => {
          console.error('发送消息时连接失败:', error);
        });
      }

      return false;
    }
  }

  processMessageQueue() {
    while (this.messageQueue.length > 0) {
      const message = this.messageQueue.shift();
      this.send(message);
    }
  }

  on(event, listener) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event).add(listener);
  }

  off(event, listener) {
    if (this.listeners.has(event)) {
      this.listeners.get(event).delete(listener);
    }
  }

  emit(event, data) {
    if (this.listeners.has(event)) {
      this.listeners.get(event).forEach((listener) => {
        try {
          listener(data);
        } catch (error) {
          console.error(`事件监听器 ${event} 执行失败:`, error);
        }
      });
    }
  }

  disconnect() {
    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }
    this.reconnectAttempts = this.maxReconnectAttempts; // 阻止重连
  }
}

// 实时数据管理器
class LiveDataManager {
  constructor() {
    this.connections = new Map();
    this.subscriptions = new Map();
  }

  async subscribe(channel, handler, options = {}) {
    const { autoReconnect = true, maxRetries = 5, retryDelay = 1000 } = options;

    let connection = this.connections.get(channel);

    if (!connection) {
      connection = new RealtimeDataService(
        `wss://api.example.com/live/${channel}`
      );
      this.connections.set(channel, connection);

      // 设置错误处理
      connection.on('maxReconnectAttemptsReached', () => {
        this.handleConnectionFailed(channel);
      });

      connection.on('parseError', ({ error, rawData }) => {
        console.error(`频道 ${channel} 消息解析错误:`, error);
      });
    }

    // 添加订阅处理器
    const subscriptionId = `${channel}_${Date.now()}_${Math.random()}`;
    this.subscriptions.set(subscriptionId, {
      channel,
      handler,
      connection,
    });

    connection.on('message', handler);

    // 连接
    const [connectError] = await ErrorUtils.to(connection.connect());

    if (connectError) {
      this.subscriptions.delete(subscriptionId);
      throw new AppError(`订阅频道 ${channel} 失败`, 'SUBSCRIPTION_FAILED');
    }

    return subscriptionId;
  }

  unsubscribe(subscriptionId) {
    const subscription = this.subscriptions.get(subscriptionId);

    if (subscription) {
      subscription.connection.off('message', subscription.handler);
      this.subscriptions.delete(subscriptionId);

      // 如果没有其他订阅,关闭连接
      const hasOtherSubscriptions = Array.from(
        this.subscriptions.values()
      ).some((sub) => sub.channel === subscription.channel);

      if (!hasOtherSubscriptions) {
        subscription.connection.disconnect();
        this.connections.delete(subscription.channel);
      }
    }
  }

  handleConnectionFailed(channel) {
    console.error(`频道 ${channel} 连接失败,已达到最大重试次数`);

    // 通知所有该频道的订阅者
    const channelSubscriptions = Array.from(this.subscriptions.values()).filter(
      (sub) => sub.channel === channel
    );

    channelSubscriptions.forEach((sub) => {
      try {
        sub.handler({
          type: 'connection_failed',
          channel,
          message: '连接失败,请稍后重试',
        });
      } catch (error) {
        console.error('通知订阅者连接失败时出错:', error);
      }
    });
  }

  // 获取连接状态
  getConnectionStatus(channel) {
    const connection = this.connections.get(channel);
    if (!connection || !connection.ws) {
      return 'disconnected';
    }

    switch (connection.ws.readyState) {
      case WebSocket.CONNECTING:
        return 'connecting';
      case WebSocket.OPEN:
        return 'connected';
      case WebSocket.CLOSING:
        return 'closing';
      case WebSocket.CLOSED:
        return 'closed';
      default:
        return 'unknown';
    }
  }
}

// Vue 3 实时数据 Hook
export function useLiveData(channel, options = {}) {
  const dataManager = new LiveDataManager();
  const data = ref(null);
  const error = ref(null);
  const status = ref('disconnected');
  const subscriptionId = ref(null);

  const subscribe = async () => {
    try {
      error.value = null;
      status.value = 'connecting';

      const id = await dataManager.subscribe(
        channel,
        (message) => {
          if (message.type === 'connection_failed') {
            error.value = new Error(message.message);
            status.value = 'error';
          } else {
            data.value = message;
            status.value = 'connected';
          }
        },
        options
      );

      subscriptionId.value = id;
      status.value = 'connected';
    } catch (err) {
      error.value = err;
      status.value = 'error';
    }
  };

  const unsubscribe = () => {
    if (subscriptionId.value) {
      dataManager.unsubscribe(subscriptionId.value);
      subscriptionId.value = null;
      status.value = 'disconnected';
    }
  };

  onMounted(() => {
    subscribe();
  });

  onUnmounted(() => {
    unsubscribe();
  });

  return {
    data: readonly(data),
    error: readonly(error),
    status: readonly(status),
    subscribe,
    unsubscribe,
    reconnect: subscribe,
  };
}

5. 表单验证和提交场景

javascript 复制代码
// 复杂表单的错误处理
class FormValidator {
  constructor() {
    this.rules = new Map();
    this.errors = new Map();
  }

  addRule(field, validator, message) {
    if (!this.rules.has(field)) {
      this.rules.set(field, []);
    }

    this.rules.get(field).push({ validator, message });
    return this;
  }

  async validate(data) {
    this.errors.clear();
    const validationPromises = [];

    for (const [field, rules] of this.rules) {
      const fieldPromise = this.validateField(field, data[field], data);
      validationPromises.push(fieldPromise);
    }

    await Promise.all(validationPromises);

    return {
      valid: this.errors.size === 0,
      errors: Object.fromEntries(this.errors),
    };
  }

  async validateField(field, value, allData) {
    const rules = this.rules.get(field) || [];

    for (const rule of rules) {
      try {
        const isValid = await rule.validator(value, allData);
        if (!isValid) {
          this.errors.set(field, rule.message);
          break; // 只显示第一个错误
        }
      } catch (error) {
        this.errors.set(field, `验证失败: ${error.message}`);
        break;
      }
    }
  }

  clearErrors() {
    this.errors.clear();
  }

  getFieldError(field) {
    return this.errors.get(field);
  }
}

// 表单提交服务
class FormSubmissionService {
  constructor() {
    this.submitting = new Set();
  }

  async submitForm(formId, data, options = {}) {
    if (this.submitting.has(formId)) {
      throw new AppError('表单正在提交中', 'FORM_SUBMITTING');
    }

    const {
      validator,
      beforeSubmit,
      afterSubmit,
      onSuccess,
      onError,
      endpoint,
      method = 'POST',
    } = options;

    this.submitting.add(formId);

    try {
      // 1. 表单验证
      if (validator) {
        const validation = await validator.validate(data);
        if (!validation.valid) {
          throw new ValidationError('表单验证失败', validation.errors);
        }
      }

      // 2. 提交前处理
      let processedData = data;
      if (beforeSubmit) {
        const [beforeError, result] = await ErrorUtils.to(beforeSubmit(data));
        if (beforeError) {
          throw new AppError('提交前处理失败', 'BEFORE_SUBMIT_FAILED');
        }
        processedData = result || data;
      }

      // 3. 提交数据
      const [submitError, response] = await ErrorUtils.to(
        api.request(endpoint, {
          method,
          body: JSON.stringify(processedData),
        })
      );

      if (submitError) {
        // 处理特定的提交错误
        if (submitError.statusCode === 422) {
          // 服务器验证错误
          const validationErrors = submitError.data?.errors || {};
          throw new ValidationError('服务器验证失败', validationErrors);
        }

        throw new AppError('提交失败,请重试', 'SUBMIT_FAILED');
      }

      // 4. 提交后处理
      if (afterSubmit) {
        await ErrorUtils.to(afterSubmit(response));
      }

      onSuccess?.(response);
      return Result.ok(response);
    } catch (error) {
      onError?.(error);
      return Result.error(error);
    } finally {
      this.submitting.delete(formId);
    }
  }

  isSubmitting(formId) {
    return this.submitting.has(formId);
  }
}

// 使用示例:用户注册表单
async function setupUserRegistrationForm() {
  const validator = new FormValidator()
    .addRule(
      'username',
      async (value) => {
        if (!value || value.length < 3) return false;

        // 检查用户名是否已存在
        const [error, result] = await ErrorUtils.to(
          api.get(`/users/check-username?username=${encodeURIComponent(value)}`)
        );

        return !error && result.available;
      },
      '用户名至少3位且不能重复'
    )
    .addRule(
      'email',
      (value) => {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(value);
      },
      '请输入有效的邮箱地址'
    )
    .addRule(
      'password',
      (value) => {
        return (
          value &&
          value.length >= 8 &&
          /[A-Z]/.test(value) &&
          /[0-9]/.test(value)
        );
      },
      '密码至少8位,包含大写字母和数字'
    )
    .addRule(
      'confirmPassword',
      (value, allData) => {
        return value === allData.password;
      },
      '两次密码输入不一致'
    );

  const formService = new FormSubmissionService();

  const submitRegistration = async (formData) => {
    const result = await formService.submitForm('registration', formData, {
      validator,
      endpoint: '/users/register',
      beforeSubmit: async (data) => {
        // 添加时间戳和设备信息
        return {
          ...data,
          timestamp: Date.now(),
          deviceInfo: {
            userAgent: navigator.userAgent,
            timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
          },
        };
      },
      afterSubmit: async (response) => {
        // 注册成功后的处理
        if (response.user) {
          localStorage.setItem('user', JSON.stringify(response.user));

          // 发送欢迎邮件
          await ErrorUtils.to(
            api.post('/users/send-welcome-email', { userId: response.user.id })
          );
        }
      },
      onSuccess: (response) => {
        showMessage('注册成功!', 'success');
        router.push('/dashboard');
      },
      onError: (error) => {
        if (error instanceof ValidationError) {
          // 显示字段验证错误
          Object.entries(error.field || {}).forEach(([field, message]) => {
            showFieldError(field, message);
          });
        } else {
          showMessage(error.message || '注册失败,请重试', 'error');
        }
      },
    });

    return result;
  };

  return {
    validator,
    submitRegistration,
    isSubmitting: (formId) => formService.isSubmitting(formId),
  };
}

🚀 性能优化和测试实例

1. 性能监控和错误追踪

javascript 复制代码
// 性能监控的错误处理
class PerformanceMonitor {
  constructor() {
    this.metrics = new Map();
    this.errorTracker = new Map();
  }

  // 监控异步操作性能
  async monitorAsyncOperation(name, operation, options = {}) {
    const {
      timeout = 30000,
      expectSuccessRate = 0.95,
      alertThreshold = 1000,
    } = options;

    const startTime = performance.now();
    const operationId = `${name}_${Date.now()}`;

    try {
      // 添加超时控制
      const result = await ErrorUtils.withTimeout(operation(), timeout);

      const duration = performance.now() - startTime;
      this.recordSuccess(name, duration);

      // 性能告警
      if (duration > alertThreshold) {
        console.warn(`操作 ${name} 耗时过长: ${duration}ms`);
        this.reportSlowOperation(name, duration, operationId);
      }

      return Result.ok(result);
    } catch (error) {
      const duration = performance.now() - startTime;
      this.recordError(name, error, duration);

      // 检查错误率
      const stats = this.getOperationStats(name);
      if (stats.errorRate > 1 - expectSuccessRate) {
        this.alertHighErrorRate(name, stats);
      }

      return Result.error(error);
    }
  }

  recordSuccess(operationName, duration) {
    const key = `${operationName}_success`;
    const records = this.metrics.get(key) || [];

    records.push({
      timestamp: Date.now(),
      duration,
      status: 'success',
    });

    // 只保留最近1000条记录
    if (records.length > 1000) {
      records.shift();
    }

    this.metrics.set(key, records);
  }

  recordError(operationName, error, duration) {
    const key = `${operationName}_error`;
    const records = this.errorTracker.get(key) || [];

    records.push({
      timestamp: Date.now(),
      duration,
      error: {
        message: error.message,
        code: error.code,
        stack: error.stack,
        type: error.constructor.name,
      },
      status: 'error',
    });

    if (records.length > 1000) {
      records.shift();
    }

    this.errorTracker.set(key, records);
  }

  getOperationStats(operationName) {
    const successKey = `${operationName}_success`;
    const errorKey = `${operationName}_error`;

    const successes = this.metrics.get(successKey) || [];
    const errors = this.errorTracker.get(errorKey) || [];

    const total = successes.length + errors.length;
    const errorRate = total > 0 ? errors.length / total : 0;

    const durations = successes.map((r) => r.duration);
    const avgDuration =
      durations.length > 0
        ? durations.reduce((a, b) => a + b, 0) / durations.length
        : 0;

    const p95Duration =
      durations.length > 0
        ? durations.sort((a, b) => a - b)[Math.floor(durations.length * 0.95)]
        : 0;

    return {
      total,
      successes: successes.length,
      errors: errors.length,
      errorRate,
      avgDuration,
      p95Duration,
      recentErrors: errors.slice(-5), // 最近5个错误
    };
  }

  async reportSlowOperation(name, duration, operationId) {
    // 上报慢操作
    const [error] = await ErrorUtils.to(
      api.post('/monitoring/slow-operations', {
        operationName: name,
        duration,
        operationId,
        timestamp: Date.now(),
        userAgent: navigator.userAgent,
        url: window.location.href,
      })
    );

    if (error) {
      console.warn('上报慢操作失败:', error);
    }
  }

  async alertHighErrorRate(name, stats) {
    console.error(`操作 ${name} 错误率过高:`, stats);

    // 发送告警
    const [error] = await ErrorUtils.to(
      api.post('/monitoring/alerts', {
        type: 'high_error_rate',
        operationName: name,
        errorRate: stats.errorRate,
        recentErrors: stats.recentErrors,
        timestamp: Date.now(),
      })
    );

    if (error) {
      console.warn('发送告警失败:', error);
    }
  }

  // 生成性能报告
  generateReport() {
    const operations = new Set();

    // 收集所有操作名称
    this.metrics.forEach((_, key) => {
      const operationName = key.replace('_success', '');
      operations.add(operationName);
    });

    this.errorTracker.forEach((_, key) => {
      const operationName = key.replace('_error', '');
      operations.add(operationName);
    });

    const report = {};
    operations.forEach((name) => {
      report[name] = this.getOperationStats(name);
    });

    return report;
  }
}

// 全局性能监控实例
const performanceMonitor = new PerformanceMonitor();

// 装饰器:自动性能监控
function withPerformanceMonitoring(operationName, options = {}) {
  return function (target, propertyKey, descriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args) {
      return await performanceMonitor.monitorAsyncOperation(
        `${target.constructor.name}.${propertyKey}`,
        () => originalMethod.apply(this, args),
        options
      );
    };

    return descriptor;
  };
}

// 使用示例
class DataService {
  @withPerformanceMonitoring('fetchUserData', {
    timeout: 5000,
    alertThreshold: 2000,
  })
  async fetchUserData(userId) {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }

  @withPerformanceMonitoring('batchUpdateUsers', {
    timeout: 30000,
    expectSuccessRate: 0.98,
  })
  async batchUpdateUsers(updates) {
    const response = await fetch('/api/users/batch', {
      method: 'PUT',
      body: JSON.stringify(updates),
    });
    return response.json();
  }
}

2. 错误处理的单元测试

javascript 复制代码
// 测试工具类
class AsyncTestUtils {
  // 模拟异步错误
  static async simulateAsyncError(errorType = 'network', delay = 100) {
    await new Promise((resolve) => setTimeout(resolve, delay));

    switch (errorType) {
      case 'network':
        throw new NetworkError('模拟网络错误');
      case 'validation':
        throw new ValidationError('模拟验证错误', 'testField');
      case 'timeout':
        throw new Error('操作超时');
      default:
        throw new Error('模拟通用错误');
    }
  }

  // 模拟成功响应
  static async simulateAsyncSuccess(data = { success: true }, delay = 100) {
    await new Promise((resolve) => setTimeout(resolve, delay));
    return data;
  }

  // 模拟间歇性失败
  static async simulateIntermittentFailure(successRate = 0.7) {
    const random = Math.random();
    if (random < successRate) {
      return this.simulateAsyncSuccess();
    } else {
      return this.simulateAsyncError();
    }
  }
}

// Jest 测试示例
describe('ErrorUtils.to() 函数测试', () => {
  test('应该正确处理成功的 Promise', async () => {
    const [error, data] = await ErrorUtils.to(
      AsyncTestUtils.simulateAsyncSuccess({ value: 42 })
    );

    expect(error).toBeNull();
    expect(data).toEqual({ value: 42 });
  });

  test('应该正确处理失败的 Promise', async () => {
    const [error, data] = await ErrorUtils.to(
      AsyncTestUtils.simulateAsyncError('network')
    );

    expect(error).toBeInstanceOf(NetworkError);
    expect(data).toBeNull();
  });

  test('应该处理超时错误', async () => {
    const timeoutPromise = new Promise((_, reject) => {
      setTimeout(() => reject(new Error('超时')), 50);
    });

    const [error, data] = await ErrorUtils.to(timeoutPromise);

    expect(error).toBeInstanceOf(Error);
    expect(error.message).toBe('超时');
    expect(data).toBeNull();
  });
});

describe('ErrorUtils.retry() 重试功能测试', () => {
  test('应该在最终成功时返回结果', async () => {
    let attempts = 0;
    const flaky = async () => {
      attempts++;
      if (attempts < 3) {
        throw new Error('失败');
      }
      return { success: true, attempts };
    };

    const [error, result] = await ErrorUtils.retry(flaky, 3, 10);

    expect(error).toBeNull();
    expect(result).toEqual({ success: true, attempts: 3 });
  });

  test('应该在达到最大重试次数后返回错误', async () => {
    const alwaysFails = async () => {
      throw new Error('总是失败');
    };

    const [error, result] = await ErrorUtils.retry(alwaysFails, 2, 10);

    expect(error).toBeInstanceOf(Error);
    expect(error.message).toBe('总是失败');
    expect(result).toBeNull();
  });
});

describe('Result 包装器测试', () => {
  test('Result.ok() 应该创建成功结果', () => {
    const result = Result.ok('test data');

    expect(result.isOk()).toBe(true);
    expect(result.isError()).toBe(false);
    expect(result.unwrap()).toBe('test data');
  });

  test('Result.error() 应该创建错误结果', () => {
    const error = new Error('test error');
    const result = Result.error(error);

    expect(result.isOk()).toBe(false);
    expect(result.isError()).toBe(true);
    expect(() => result.unwrap()).toThrow('test error');
  });

  test('Result.from() 应该正确包装 Promise', async () => {
    const successResult = await Result.from(
      AsyncTestUtils.simulateAsyncSuccess('success')
    );

    expect(successResult.isOk()).toBe(true);
    expect(successResult.unwrap()).toBe('success');

    const errorResult = await Result.from(AsyncTestUtils.simulateAsyncError());

    expect(errorResult.isError()).toBe(true);
  });

  test('map() 应该正确转换成功值', () => {
    const result = Result.ok(5);
    const mapped = result.map((x) => x * 2);

    expect(mapped.unwrap()).toBe(10);
  });

  test('map() 应该跳过错误值', () => {
    const result = Result.error(new Error('test'));
    const mapped = result.map((x) => x * 2);

    expect(mapped.isError()).toBe(true);
  });
});

3. 集成测试和 E2E 测试

javascript 复制代码
// Cypress E2E 测试示例
describe('错误处理集成测试', () => {
  beforeEach(() => {
    // 模拟网络错误
    cy.intercept('GET', '/api/users/*', { forceNetworkError: true }).as(
      'userError'
    );
    cy.visit('/user-profile');
  });

  it('应该优雅地处理网络错误', () => {
    // 等待错误处理
    cy.wait('@userError');

    // 验证错误消息显示
    cy.contains('网络异常,请稍后重试').should('be.visible');

    // 验证重试按钮可用
    cy.get('[data-testid="retry-button"]').should('be.enabled');

    // 验证降级内容显示
    cy.get('[data-testid="fallback-content"]').should('be.visible');
  });

  it('应该在网络恢复后自动重试', () => {
    // 模拟网络恢复
    cy.intercept('GET', '/api/users/*', { fixture: 'user.json' }).as(
      'userSuccess'
    );

    // 点击重试
    cy.get('[data-testid="retry-button"]').click();
    cy.wait('@userSuccess');

    // 验证数据正确显示
    cy.get('[data-testid="user-name"]').should('contain', 'John Doe');
    cy.get('[data-testid="error-message"]').should('not.exist');
  });
});

// Playwright 测试示例
import { test, expect } from '@playwright/test';

test.describe('异步错误处理', () => {
  test('文件上传错误处理', async ({ page }) => {
    await page.goto('/upload');

    // 模拟服务器错误
    await page.route('/api/upload/**', (route) => {
      route.fulfill({
        status: 500,
        body: JSON.stringify({ error: '服务器内部错误' }),
      });
    });

    // 上传文件
    const fileInput = page.locator('input[type="file"]');
    await fileInput.setInputFiles('./test-files/sample.pdf');

    // 验证错误处理
    await expect(page.locator('[data-testid="upload-error"]')).toBeVisible();
    await expect(page.locator('[data-testid="upload-error"]')).toContainText(
      '上传失败'
    );

    // 验证重试功能
    await page.locator('[data-testid="retry-upload"]').click();
    await expect(page.locator('[data-testid="upload-progress"]')).toBeVisible();
  });

  test('实时数据连接错误处理', async ({ page }) => {
    // 模拟 WebSocket 连接失败
    await page.addInitScript(() => {
      window.WebSocket = class {
        constructor() {
          setTimeout(() => {
            this.onerror && this.onerror(new Event('error'));
          }, 100);
        }
      };
    });

    await page.goto('/dashboard');

    // 验证错误状态显示
    await expect(
      page.locator('[data-testid="connection-status"]')
    ).toContainText('连接失败');
    await expect(
      page.locator('[data-testid="reconnect-button"]')
    ).toBeVisible();

    // 验证离线模式
    await expect(
      page.locator('[data-testid="offline-indicator"]')
    ).toBeVisible();
  });
});

4. 性能基准测试

javascript 复制代码
// 性能基准测试
class PerformanceBenchmark {
  static async benchmarkErrorHandling() {
    const iterations = 1000;
    const results = {};

    // 测试传统 try-catch
    console.time('traditional-try-catch');
    for (let i = 0; i < iterations; i++) {
      try {
        await AsyncTestUtils.simulateIntermittentFailure(0.8);
      } catch (error) {
        // 处理错误
      }
    }
    console.timeEnd('traditional-try-catch');

    // 测试 to() 函数
    console.time('to-function');
    for (let i = 0; i < iterations; i++) {
      const [error, result] = await ErrorUtils.to(
        AsyncTestUtils.simulateIntermittentFailure(0.8)
      );
    }
    console.timeEnd('to-function');

    // 测试 Result 包装器
    console.time('result-wrapper');
    for (let i = 0; i < iterations; i++) {
      const result = await Result.from(
        AsyncTestUtils.simulateIntermittentFailure(0.8)
      );
    }
    console.timeEnd('result-wrapper');

    // 内存使用情况
    if (performance.memory) {
      results.memoryUsage = {
        used: performance.memory.usedJSHeapSize,
        total: performance.memory.totalJSHeapSize,
        limit: performance.memory.jsHeapSizeLimit,
      };
    }

    return results;
  }

  static async stressTest() {
    const concurrentOperations = 100;
    const operations = Array.from({ length: concurrentOperations }, (_, i) =>
      ErrorUtils.to(AsyncTestUtils.simulateIntermittentFailure(0.7))
    );

    const startTime = performance.now();
    const results = await Promise.all(operations);
    const endTime = performance.now();

    const successes = results.filter(([error]) => !error).length;
    const failures = results.filter(([error]) => error).length;

    return {
      duration: endTime - startTime,
      totalOperations: concurrentOperations,
      successes,
      failures,
      successRate: successes / concurrentOperations,
      opsPerSecond: concurrentOperations / ((endTime - startTime) / 1000),
    };
  }
}

// 运行基准测试
async function runBenchmarks() {
  console.log('开始性能基准测试...');

  const errorHandlingBenchmark =
    await PerformanceBenchmark.benchmarkErrorHandling();
  console.log('错误处理基准测试结果:', errorHandlingBenchmark);

  const stressTestResult = await PerformanceBenchmark.stressTest();
  console.log('压力测试结果:', stressTestResult);

  // 生成报告
  const report = {
    timestamp: new Date().toISOString(),
    userAgent: navigator.userAgent,
    errorHandling: errorHandlingBenchmark,
    stressTest: stressTestResult,
  };

  // 保存或上传报告
  localStorage.setItem('performance-report', JSON.stringify(report));

  return report;
}

5. 错误边界和全局错误处理

javascript 复制代码
// React 错误边界
class AsyncErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null, errorInfo: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    this.setState({
      error,
      errorInfo,
    });

    // 异步错误上报
    this.reportError(error, errorInfo);
  }

  async reportError(error, errorInfo) {
    const [reportError] = await ErrorUtils.to(
      api.post('/errors/report', {
        message: error.message,
        stack: error.stack,
        componentStack: errorInfo.componentStack,
        timestamp: Date.now(),
        url: window.location.href,
        userAgent: navigator.userAgent,
      })
    );

    if (reportError) {
      console.warn('错误上报失败:', reportError);
    }
  }

  render() {
    if (this.state.hasError) {
      return (
        this.props.fallback || (
          <div className="error-boundary">
            <h2>出现了一些问题</h2>
            <details style={{ whiteSpace: 'pre-wrap' }}>
              {this.state.error && this.state.error.toString()}
              <br />
              {this.state.errorInfo.componentStack}
            </details>
            <button
              onClick={() => this.setState({ hasError: false, error: null })}
            >
              重试
            </button>
          </div>
        )
      );
    }

    return this.props.children;
  }
}

// Vue 3 全局错误处理
const app = createApp(App);

app.config.errorHandler = async (error, instance, info) => {
  console.error('Vue 全局错误:', error, info);

  // 异步错误上报
  const [reportError] = await ErrorUtils.to(
    api.post('/errors/vue-report', {
      message: error.message,
      stack: error.stack,
      componentName: instance?.$options.name || 'Unknown',
      errorInfo: info,
      timestamp: Date.now(),
      route: instance?.$route?.path,
    })
  );

  if (reportError) {
    console.warn('Vue 错误上报失败:', reportError);
  }
};

// 全局未捕获错误处理
window.addEventListener('error', async (event) => {
  const [reportError] = await ErrorUtils.to(
    api.post('/errors/global', {
      message: event.message,
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno,
      stack: event.error?.stack,
      timestamp: Date.now(),
    })
  );

  if (reportError) {
    console.warn('全局错误上报失败:', reportError);
  }
});

// 未捕获的 Promise 错误
window.addEventListener('unhandledrejection', async (event) => {
  console.error('未处理的 Promise 拒绝:', event.reason);

  const [reportError] = await ErrorUtils.to(
    api.post('/errors/unhandled-promise', {
      reason: event.reason?.message || String(event.reason),
      stack: event.reason?.stack,
      timestamp: Date.now(),
    })
  );

  if (reportError) {
    console.warn('Promise 错误上报失败:', reportError);
  }

  // 阻止控制台错误输出
  event.preventDefault();
});

📊 错误处理最佳实践总结

1. 选择指南

场景 推荐方案 原因
简单异步操作 to() 函数 语法简洁,易于理解
复杂业务逻辑 Result 包装器 类型安全,链式操作
重复性操作 装饰器模式 代码复用,统一处理
表单验证 专用验证器 业务专一,错误详细
实时数据 连接管理器 自动重连,状态管理
性能敏感 性能监控 及时发现,主动优化

2. 性能对比

方案 内存开销 执行速度 代码复杂度 可维护性
try-catch
to() 函数
Result 包装器
装饰器模式

3. 团队采用建议

  1. 渐进式采用 :从 to() 函数开始,逐步引入其他模式
  2. 统一规范:制定团队编码规范,保持一致性
  3. 工具支持:配置 ESLint 规则,IDE 代码片段
  4. 培训推广:团队分享,代码审查
  5. 监控反馈:收集使用反馈,持续优化

本文档持续更新中,欢迎贡献更多优雅的错误处理模式!

相关推荐
数据知道2 小时前
Go基础:Go语言能用到的常用时间处理
开发语言·后端·golang·go语言
excel2 小时前
楖览:Vue3 源码研究导读
前端
proud12122 小时前
开源的 CSS 动画库
前端·css·开源
折翼的恶魔2 小时前
HTML媒体标签
前端·html
excel3 小时前
前端项目中的测试分类与实践 —— 以 Vue 项目为例
前端
宋辰月3 小时前
echarts项目积累
前端·javascript·echarts
go_bai3 小时前
Linux--常见工具
linux·开发语言·经验分享·笔记·vim·学习方法
du青松3 小时前
onlyoffice 服务搭建及配置 - 前端 office 文件预览解决方案
前端
ajassi20003 小时前
开源 C# 快速开发(三)复杂控件
开发语言·开源·c#