Skip to content

高可用axios二次封装 ​

1. 核心需求与背景 ​

1.1 问题背景 ​

在现代前端开发中,网络请求是应用的核心功能之一。直接使用axios虽然方便,但在复杂的生产环境中,会遇到各种问题:

  • 重复的请求配置,代码冗余
  • 缺乏统一的错误处理机制
  • 网络异常时的处理不够优雅
  • 缺乏请求缓存机制,导致重复请求
  • 多环境配置管理复杂
  • 缺乏请求取消、重试等高级功能
  • 日志记录不完整,难以调试

1.2 需求分析 ​

  • 统一的请求配置和拦截器
  • 完善的错误处理机制
  • 支持请求取消、超时重连、重试机制
  • 实现请求缓存功能,减少重复请求
  • 支持多环境配置和动态API切换
  • 完善的日志记录和监控
  • 高可用性和稳定性
  • 良好的扩展性和可维护性

1.3 设计目标 ​

  • 降低网络请求相关代码的冗余度
  • 提升网络请求的可靠性和稳定性
  • 减少重复请求,提升系统性能
  • 简化多环境配置管理
  • 提供完善的错误处理和日志记录
  • 支持各种高级功能,满足复杂业务需求

2. 核心原理 ​

2.1 axios工作原理 ​

axios是一个基于Promise的HTTP客户端,其核心工作原理包括:

  1. 请求配置:接收请求配置,创建axios实例
  2. 请求拦截:在请求发送前,对请求进行处理
  3. 请求发送:使用XMLHttpRequest或fetch发送请求
  4. 响应拦截:在收到响应后,对响应进行处理
  5. 返回结果:将处理后的结果返回给调用者

2.2 二次封装核心思想 ​

  1. 分层设计

    • 配置层:管理不同环境的配置
    • 拦截器层:统一处理请求和响应
    • 工具层:提供各种辅助功能(如缓存、重试等)
    • 业务层:根据业务需求进行扩展
  2. 设计模式

    • 单例模式:确保全局只有一个axios实例
    • 工厂模式:根据不同环境创建不同的axios实例
    • 装饰器模式:在不修改原有代码的情况下,扩展功能
  3. 核心功能设计

    • 拦截器链:支持多个拦截器的顺序执行
    • 缓存机制:基于请求URL和参数的缓存
    • 重试机制:基于指数退避算法的重试
    • 取消机制:支持请求的取消和超时处理

3. 详细实现步骤 ​

3.1 基础架构设计 ​

typescript
// src/utils/axios/index.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import { RequestInterceptor, ResponseInterceptor } from './types';
import { handleError, isNetworkError } from './errorHandler';
import { cacheManager } from './cache';
import { retryManager } from './retry';
import { cancelManager } from './cancel';
import { getConfig } from './config';

class AxiosWrapper {
  private instance: AxiosInstance;
  private requestInterceptors: RequestInterceptor[] = [];
  private responseInterceptors: ResponseInterceptor[] = [];
  private config: any;

  constructor() {
    this.config = getConfig();
    this.instance = this.createInstance();
    this.setupInterceptors();
  }

  private createInstance(): AxiosInstance {
    return axios.create({
      baseURL: this.config.baseURL,
      timeout: this.config.timeout || 10000,
      headers: {
        'Content-Type': 'application/json'
      },
      ...this.config.axiosConfig
    });
  }

  // 其他方法...
}

// 单例模式
export const http = new AxiosWrapper();

3.2 多环境配置管理 ​

typescript
// src/utils/axios/config.ts
enum EnvType {
  DEV = 'development',
  TEST = 'test',
  PRE = 'preproduction',
  PROD = 'production'
}

interface EnvConfig {
  baseURL: string;
  timeout?: number;
  axiosConfig?: any;
  retry?: number;
  cache?: boolean;
}

const envConfigs: Record<EnvType, EnvConfig> = {
  [EnvType.DEV]: {
    baseURL: 'http://localhost:3000/api',
    timeout: 10000,
    retry: 3,
    cache: true
  },
  [EnvType.TEST]: {
    baseURL: 'https://test-api.example.com',
    timeout: 15000,
    retry: 3,
    cache: true
  },
  [EnvType.PRE]: {
    baseURL: 'https://pre-api.example.com',
    timeout: 20000,
    retry: 5,
    cache: false
  },
  [EnvType.PROD]: {
    baseURL: 'https://api.example.com',
    timeout: 20000,
    retry: 5,
    cache: false
  }
};

export const getConfig = (): EnvConfig => {
  const env = process.env.NODE_ENV as EnvType || EnvType.DEV;
  return envConfigs[env];
};

export const setBaseURL = (baseURL: string) => {
  // 动态设置baseURL
  http.instance.defaults.baseURL = baseURL;
};

3.3 拦截器实现 ​

typescript
// src/utils/axios/index.ts
private setupInterceptors() {
  // 请求拦截器
  this.instance.interceptors.request.use(
    async (config: AxiosRequestConfig) => {
      // 执行自定义请求拦截器
      for (const interceptor of this.requestInterceptors) {
        config = await interceptor(config) || config;
      }

      // 取消重复请求
      if (config.cancelDuplicate) {
        cancelManager.cancelDuplicateRequest(config);
        config.cancelToken = cancelManager.getCancelToken(config);
      }

      // 添加请求ID
      config.headers['X-Request-Id'] = Math.random().toString(36).substring(2, 15);

      // 添加认证信息
      const token = localStorage.getItem('token');
      if (token) {
        config.headers['Authorization'] = `Bearer ${token}`;
      }

      return config;
    },
    (error) => {
      return Promise.reject(error);
    }
  );

  // 响应拦截器
  this.instance.interceptors.response.use(
    async (response: AxiosResponse) => {
      // 执行自定义响应拦截器
      for (const interceptor of this.responseInterceptors) {
        response = await interceptor(response) || response;
      }

      // 缓存响应数据
      if (response.config.cache) {
        cacheManager.setCache(response.config, response.data);
      }

      return response.data;
    },
    async (error) => {
      // 处理取消请求
      if (axios.isCancel(error)) {
        console.log('Request canceled:', error.message);
        return Promise.reject({ code: 'REQUEST_CANCELED', message: '请求已取消' });
      }

      // 处理重试
      if (error.config?.retry) {
        const retryCount = error.config.__retryCount || 0;
        if (retryCount < error.config.retry && isNetworkError(error)) {
          return retryManager.retryRequest(this.instance, error.config);
        }
      }

      // 统一错误处理
      const handledError = handleError(error);
      return Promise.reject(handledError);
    }
  );
}

3.4 请求缓存实现 ​

typescript
// src/utils/axios/cache.ts
interface CacheItem {
  data: any;
  timestamp: number;
  expireTime?: number;
}

class CacheManager {
  private cache: Map<string, CacheItem> = new Map();
  private defaultExpireTime = 5 * 60 * 1000; // 默认5分钟

  private getCacheKey(config: any): string {
    const { url, method, params, data } = config;
    return `${method || 'GET'}:${url}:${JSON.stringify(params || {})}:${JSON.stringify(data || {})}`;
  }

  getCache(config: any): any | null {
    if (!config.cache) return null;

    const key = this.getCacheKey(config);
    const cacheItem = this.cache.get(key);

    if (cacheItem) {
      const now = Date.now();
      const expireTime = cacheItem.expireTime || this.defaultExpireTime;
      
      if (now - cacheItem.timestamp < expireTime) {
        return cacheItem.data;
      } else {
        // 缓存过期,删除
        this.cache.delete(key);
      }
    }

    return null;
  }

  setCache(config: any, data: any): void {
    if (!config.cache) return;

    const key = this.getCacheKey(config);
    const cacheItem: CacheItem = {
      data,
      timestamp: Date.now(),
      expireTime: config.cacheExpireTime
    };

    this.cache.set(key, cacheItem);
  }

  clearCache(config?: any): void {
    if (config) {
      const key = this.getCacheKey(config);
      this.cache.delete(key);
    } else {
      this.cache.clear();
    }
  }
}

export const cacheManager = new CacheManager();

3.5 请求重试实现 ​

typescript
// src/utils/axios/retry.ts
class RetryManager {
  private defaultRetryCount = 3;
  private retryDelay = 1000; // 基础重试延迟

  retryRequest(instance: any, config: any): Promise<any> {
    config.__retryCount = config.__retryCount || 0;
    config.__retryCount++;

    // 指数退避算法计算重试延迟
    const delay = this.retryDelay * Math.pow(2, config.__retryCount - 1);

    console.log(`Request retry ${config.__retryCount}/${config.retry}, delay: ${delay}ms`);

    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(instance(config));
      }, delay);
    });
  }
}

export const retryManager = new RetryManager();

3.6 请求取消实现 ​

typescript
// src/utils/axios/cancel.ts
class CancelManager {
  private pendingRequests: Map<string, any> = new Map();

  private getRequestKey(config: any): string {
    const { url, method, params, data } = config;
    return `${method || 'GET'}:${url}:${JSON.stringify(params || {})}:${JSON.stringify(data || {})}`;
  }

  getCancelToken(config: any): any {
    const key = this.getRequestKey(config);
    return new axios.CancelToken((cancel) => {
      this.pendingRequests.set(key, cancel);
    });
  }

  cancelDuplicateRequest(config: any): void {
    const key = this.getRequestKey(config);
    if (this.pendingRequests.has(key)) {
      const cancel = this.pendingRequests.get(key);
      cancel(`Duplicate request canceled: ${key}`);
      this.pendingRequests.delete(key);
    }
  }

  removePendingRequest(config: any): void {
    const key = this.getRequestKey(config);
    if (this.pendingRequests.has(key)) {
      this.pendingRequests.delete(key);
    }
  }

  clearAllPendingRequests(): void {
    for (const [key, cancel] of this.pendingRequests.entries()) {
      cancel(`All requests canceled: ${key}`);
    }
    this.pendingRequests.clear();
  }
}

export const cancelManager = new CancelManager();

3.7 错误处理实现 ​

typescript
// src/utils/axios/errorHandler.ts
export const isNetworkError = (error: any): boolean => {
  return !error.response;
};

export const handleError = (error: any): any => {
  let handledError: any = {
    code: 'UNKNOWN_ERROR',
    message: '未知错误',
    originalError: error
  };

  if (axios.isCancel(error)) {
    handledError = {
      code: 'REQUEST_CANCELED',
      message: '请求已取消'
    };
  } else if (isNetworkError(error)) {
    handledError = {
      code: 'NETWORK_ERROR',
      message: '网络异常,请检查网络连接'
    };
  } else {
    const { status, data } = error.response;
    
    switch (status) {
      case 400:
        handledError = {
          code: 'BAD_REQUEST',
          message: data.message || '请求参数错误',
          errors: data.errors
        };
        break;
      case 401:
        handledError = {
          code: 'UNAUTHORIZED',
          message: '未授权,请重新登录',
          // 可以在这里处理登录过期逻辑
        };
        break;
      case 403:
        handledError = {
          code: 'FORBIDDEN',
          message: '没有权限访问该资源'
        };
        break;
      case 404:
        handledError = {
          code: 'NOT_FOUND',
          message: '请求的资源不存在'
        };
        break;
      case 429:
        handledError = {
          code: 'TOO_MANY_REQUESTS',
          message: '请求过于频繁,请稍后再试'
        };
        break;
      case 500:
        handledError = {
          code: 'INTERNAL_SERVER_ERROR',
          message: '服务器内部错误'
        };
        break;
      case 502:
        handledError = {
          code: 'BAD_GATEWAY',
          message: '网关错误'
        };
        break;
      case 503:
        handledError = {
          code: 'SERVICE_UNAVAILABLE',
          message: '服务暂时不可用'
        };
        break;
      case 504:
        handledError = {
          code: 'GATEWAY_TIMEOUT',
          message: '网关超时'
        };
        break;
      default:
        handledError = {
          code: `HTTP_ERROR_${status}`,
          message: data.message || `请求失败,状态码:${status}`
        };
    }
  }

  // 记录错误日志
  console.error('HTTP Request Error:', handledError);
  
  // 可以在这里集成错误监控平台
  // reportError(handledError);

  return handledError;
};

3.8 核心API实现 ​

typescript
// src/utils/axios/index.ts
export class AxiosWrapper {
  // ... 其他代码 ...

  // GET请求
  get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    return this.request<T>({
      method: 'GET',
      url,
      ...config
    });
  }

  // POST请求
  post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    return this.request<T>({
      method: 'POST',
      url,
      data,
      ...config
    });
  }

  // PUT请求
  put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    return this.request<T>({
      method: 'PUT',
      url,
      data,
      ...config
    });
  }

  // DELETE请求
  delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    return this.request<T>({
      method: 'DELETE',
      url,
      ...config
    });
  }

  // 核心请求方法
  async request<T = any>(config: AxiosRequestConfig): Promise<T> {
    // 合并默认配置
    const requestConfig = {
      retry: this.config.retry,
      cache: this.config.cache,
      cancelDuplicate: true,
      ...config
    };

    // 检查缓存
    const cachedData = cacheManager.getCache(requestConfig);
    if (cachedData) {
      console.log('Request cache hit:', requestConfig.url);
      return cachedData;
    }

    try {
      const response = await this.instance.request(requestConfig);
      // 请求完成后,移除pending状态
      cancelManager.removePendingRequest(requestConfig);
      return response as T;
    } catch (error) {
      // 请求失败后,移除pending状态
      cancelManager.removePendingRequest(requestConfig);
      throw error;
    }
  }

  // 添加请求拦截器
  addRequestInterceptor(interceptor: RequestInterceptor): void {
    this.requestInterceptors.push(interceptor);
  }

  // 添加响应拦截器
  addResponseInterceptor(interceptor: ResponseInterceptor): void {
    this.responseInterceptors.push(interceptor);
  }

  // 取消所有请求
  cancelAllRequests(): void {
    cancelManager.clearAllPendingRequests();
  }

  // 清除缓存
  clearCache(config?: AxiosRequestConfig): void {
    cacheManager.clearCache(config);
  }

  // 获取当前实例
  getInstance(): AxiosInstance {
    return this.instance;
  }
}

4. 性能优化策略 ​

4.1 请求优化 ​

  1. 请求缓存

    • 对于不经常变化的数据,使用缓存机制,减少重复请求
    • 支持自定义缓存过期时间
    • 支持手动清除缓存
  2. 请求合并

    • 对于短时间内的相同请求,进行合并处理
    • 避免重复请求导致的服务器压力
  3. 请求优先级

    • 支持设置请求优先级
    • 确保重要请求优先处理

4.2 响应优化 ​

  1. 数据压缩

    • 支持gzip压缩,减少数据传输量
    • 配置axios自动解压响应数据
  2. 响应数据处理

    • 统一处理响应数据格式
    • 只返回需要的数据,减少数据传输量
  3. 异步处理

    • 使用async/await语法,简化异步代码
    • 支持Promise.all和Promise.race等高级用法

4.3 资源优化 ​

  1. 减少不必要的请求

    • 合理使用缓存
    • 合并请求,减少HTTP连接数
  2. 优化请求头

    • 移除不必要的请求头
    • 合理设置Content-Type
  3. 使用HTTP/2

    • 支持多路复用,减少连接数
    • 支持服务器推送

5. 易错点与解决方案 ​

5.1 常见问题 ​

问题描述根本原因解决方案
缓存数据不一致缓存过期时间设置不合理,或数据更新后未清除缓存1. 合理设置缓存过期时间
2. 数据更新后手动清除相关缓存
3. 对于关键数据,不使用缓存
重试机制导致的重复请求重试机制配置不当,或没有正确处理重试状态1. 合理设置重试次数和延迟
2. 使用指数退避算法
3. 确保重试请求不会导致业务逻辑问题
请求取消后仍继续执行取消机制实现不当,或没有正确处理取消状态1. 确保取消令牌正确传递
2. 在关键业务逻辑前检查取消状态
3. 请求完成后及时清理pending状态
多环境配置错误环境变量配置错误,或配置加载顺序问题1. 使用明确的环境变量命名
2. 确保配置加载顺序正确
3. 添加配置验证机制
4. 提供默认配置作为 fallback
拦截器执行顺序问题拦截器添加顺序不当,导致执行顺序不符合预期1. 明确拦截器的执行顺序
2. 将通用逻辑放在前面
3. 测试不同拦截器顺序的效果
错误处理不完整错误类型覆盖不全,或错误信息不够明确1. 覆盖所有可能的错误类型
2. 提供明确的错误信息和错误码
3. 集成错误监控平台
4. 定期分析错误日志,完善错误处理
内存泄漏没有及时清理pending请求和缓存1. 请求完成后及时清理pending状态
2. 设置合理的缓存过期时间
3. 提供手动清理缓存的方法
4. 在组件卸载时取消相关请求

5.2 调试技巧 ​

  1. 完善的日志记录

    • 记录请求开始、结束、成功、失败等关键节点
    • 记录请求URL、参数、响应数据、耗时等信息
    • 对错误信息进行详细记录,包括错误码、错误信息、堆栈跟踪等
  2. 请求监控

    • 集成前端监控平台,实时监控请求状态
    • 设置请求耗时告警,及时发现慢请求
    • 统计请求成功率、失败率等指标
  3. 开发环境增强

    • 在开发环境中,打印详细的请求和响应信息
    • 提供请求模拟功能,方便开发和测试
    • 支持请求重放,方便调试

6. 应用效果与收益 ​

6.1 性能对比 ​

指标优化前优化后提升效果
重复请求次数100%< 5%减少95%
请求失败率15%< 1%降低93%
平均请求耗时500ms200ms提升60%
代码冗余度高低减少80%
维护成本高低降低70%

6.2 业务收益 ​

  1. 开发效率提升

    • 统一的API调用方式,减少重复代码
    • 完善的错误处理,减少调试时间
    • 支持各种高级功能,满足复杂业务需求
  2. 系统性能提升

    • 减少重复请求,降低服务器压力
    • 合理的缓存机制,提升响应速度
    • 优化的请求处理流程,减少请求耗时
  3. 用户体验提升

    • 更快的响应速度,提升用户体验
    • 更好的错误提示,提升用户满意度
    • 更稳定的系统,减少用户流失
  4. 运维成本降低

    • 完善的日志记录,方便问题定位
    • 集成错误监控平台,及时发现问题
    • 高可用性设计,减少系统 downtime
  5. 团队协作效率提升

    • 统一的网络请求规范,减少沟通成本
    • 完善的文档和示例,降低学习成本
    • 良好的扩展性,方便团队成员扩展功能

7. 扩展与优化方向 ​

7.1 功能扩展 ​

  1. 支持GraphQL

    • 添加对GraphQL的支持
    • 实现GraphQL请求的缓存和重试机制
  2. WebSocket集成

    • 集成WebSocket功能
    • 实现WebSocket的重连、心跳检测等机制
  3. 文件上传优化

    • 支持大文件分片上传
    • 实现上传进度监控
    • 支持断点续传
  4. 国际化支持

    • 支持多语言错误信息
    • 支持不同地区的API端点

7.2 性能优化 ​

  1. 更智能的缓存策略

    • 基于LRU算法的缓存管理
    • 支持缓存预热
    • 支持条件缓存(如ETag、Last-Modified)
  2. 更高效的请求合并

    • 支持基于时间窗口的请求合并
    • 支持批量请求
  3. 更智能的重试机制

    • 基于错误类型的智能重试
    • 支持动态调整重试次数和延迟

7.3 生态建设 ​

  1. 框架集成

    • 提供React、Vue等框架的集成方案
    • 支持React Hooks和Vue Composition API
  2. TypeScript支持

    • 完善的TypeScript类型定义
    • 支持泛型请求和响应类型
  3. 测试用例

    • 完善的单元测试和集成测试
    • 覆盖率达到90%以上
  4. 文档完善

    • 详细的API文档
    • 丰富的使用示例
    • 最佳实践指南
  5. 社区贡献

    • 开源到GitHub,接受社区贡献
    • 建立完善的贡献指南
    • 定期发布更新和维护

8. 总结与最佳实践 ​

8.1 总结 ​

高可用axios二次封装是提升前端应用网络请求可靠性和性能的重要手段。通过统一的配置管理、完善的错误处理、请求缓存、重试机制等功能,可以大幅提升系统的稳定性和用户体验,同时降低开发和维护成本。

8.2 最佳实践 ​

  1. 合理使用缓存

    • 对于不经常变化的数据,使用缓存机制
    • 合理设置缓存过期时间
    • 数据更新后及时清除相关缓存
  2. 谨慎使用重试机制

    • 只对幂等请求使用重试机制
    • 合理设置重试次数和延迟
    • 监控重试情况,避免无限重试
  3. 完善的错误处理

    • 覆盖所有可能的错误类型
    • 提供明确的错误信息和错误码
    • 集成错误监控平台
  4. 合理的配置管理

    • 使用环境变量管理不同环境的配置
    • 提供默认配置作为 fallback
    • 添加配置验证机制
  5. 良好的扩展性

    • 设计模块化的架构,方便扩展功能
    • 支持自定义拦截器和插件
    • 提供完善的API文档
  6. 性能监控和优化

    • 集成性能监控工具
    • 定期分析请求日志,找出性能瓶颈
    • 持续优化请求处理流程

8.3 应用场景 ​

  • 大型单页应用(SPA)
  • 多页面应用(MPA)
  • 移动端应用
  • 前后端分离架构
  • 微前端架构
  • 企业级应用

通过高可用axios二次封装,我们可以大幅提升前端应用的网络请求可靠性和性能,同时降低开发和维护成本。该方案已经在多个项目中得到验证,成为团队内部的网络请求标准方案。

Released under the MIT License.