作为前端架构师,封装axios需要考虑以下几个关键方面:

  1. 基础封装类:
class HttpClient {
  constructor(config = {}) {
    // 创建axios实例
    this.instance = axios.create({
      baseURL: config.baseURL || process.env.API_BASE_URL,
      timeout: config.timeout || 30000,
      withCredentials: true,
      ...config
    })
    
    // 初始化拦截器
    this.initInterceptors()
    
    // 保存pending请求
    this.pendingRequests = new Map()
  }

  // 初始化拦截器
  initInterceptors() {
    // 请求拦截
    this.instance.interceptors.request.use(
      (config) => {
        // 防重复请求
        this.removePendingRequest(config)
        this.addPendingRequest(config)
        
        // 添加token
        const token = localStorage.getItem('token')
        if (token) {
          config.headers.Authorization = `Bearer ${token}`
        }
        
        // 添加时间戳防止缓存
        if (config.method === 'get') {
          config.params = { 
            ...config.params, 
            _t: Date.now() 
          }
        }
        
        return config
      },
      (error) => {
        return Promise.reject(error)
      }
    )

    // 响应拦截
    this.instance.interceptors.response.use(
      (response) => {
        // 移除完成的请求
        this.removePendingRequest(response.config)
        
        // 统一处理响应
        return this.handleResponse(response)
      },
      (error) => {
        // 移除失败的请求
        error.config && this.removePendingRequest(error.config)
        
        // 统一错误处理
        return this.handleError(error)
      }
    )
  }

  // 处理响应数据
  handleResponse(response) {
    const res = response.data
    
    // 假设后端返回格式为 { code, data, message }
    if (res.code === 0) {
      return res.data
    }
    
    // 处理特定错误码
    switch (res.code) {
      case 401:
        this.handleUnauthorized()
        break
      case 403:
        this.handleForbidden()
        break
      // ... 其他状态码处理
    }
    
    return Promise.reject(new Error(res.message))
  }

  // 统一错误处理
  handleError(error) {
    // 处理请求取消
    if (axios.isCancel(error)) {
      return Promise.reject(new Error('Request cancelled'))
    }
    
    // 处理超时
    if (error.code === 'ECONNABORTED') {
      return Promise.reject(new Error('Request timeout'))
    }
    
    // 处理网络错误
    if (!error.response) {
      return Promise.reject(new Error('Network error'))
    }
    
    // 处理HTTP状态码
    const status = error.response.status
    switch (status) {
      case 401:
        this.handleUnauthorized()
        break
      case 403:
        this.handleForbidden()
        break
      case 404:
        return Promise.reject(new Error('Resource not found'))
      case 500:
        return Promise.reject(new Error('Server error'))
      default:
        return Promise.reject(error)
    }
  }

  // 防重复请求处理
  generateRequestKey(config) {
    const { method, url, params, data } = config
    return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&')
  }

  addPendingRequest(config) {
    const requestKey = this.generateRequestKey(config)
    config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
      if (!this.pendingRequests.has(requestKey)) {
        this.pendingRequests.set(requestKey, cancel)
      }
    })
  }

  removePendingRequest(config) {
    const requestKey = this.generateRequestKey(config)
    if (this.pendingRequests.has(requestKey)) {
      const cancel = this.pendingRequests.get(requestKey)
      cancel()
      this.pendingRequests.delete(requestKey)
    }
  }

  // 请求方法封装
  async request(config) {
    try {
      const response = await this.instance.request(config)
      return response
    } catch (error) {
      return Promise.reject(error)
    }
  }

  async get(url, params = {}, config = {}) {
    return this.request({
      method: 'get',
      url,
      params,
      ...config
    })
  }

  async post(url, data = {}, config = {}) {
    return this.request({
      method: 'post',
      url,
      data,
      ...config
    })
  }

  // ... 其他HTTP方法
}
  1. 请求重试机制:
class RetryHttpClient extends HttpClient {
  constructor(config = {}) {
    super(config)
    this.retryTimes = config.retryTimes || 3
    this.retryDelay = config.retryDelay || 1000
  }

  async request(config) {
    const retryConfig = {
      retryTimes: config.retryTimes || this.retryTimes,
      retryDelay: config.retryDelay || this.retryDelay,
      currentRetry: 0
    }

    const requestWithRetry = async () => {
      try {
        return await super.request(config)
      } catch (error) {
        if (this.shouldRetry(error, retryConfig)) {
          retryConfig.currentRetry++
          await this.sleep(retryConfig.retryDelay)
          return requestWithRetry()
        }
        throw error
      }
    }

    return requestWithRetry()
  }

  shouldRetry(error, config) {
    return (
      config.currentRetry < config.retryTimes &&
      !axios.isCancel(error) &&
      error.response?.status >= 500
    )
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
  }
}
  1. 缓存处理:
class CacheHttpClient extends HttpClient {
  constructor(config = {}) {
    super(config)
    this.cache = new Map()
    this.cacheTime = config.cacheTime || 5 * 60 * 1000 // 默认5分钟
  }

  async get(url, params = {}, config = {}) {
    if (config.useCache) {
      const cacheKey = this.getCacheKey(url, params)
      const cachedData = this.getCache(cacheKey)
      
      if (cachedData) {
        return cachedData
      }
      
      const response = await super.get(url, params, config)
      this.setCache(cacheKey, response, config.cacheTime)
      return response
    }
    
    return super.get(url, params, config)
  }

  getCacheKey(url, params) {
    return `${url}?${JSON.stringify(params)}`
  }

  getCache(key) {
    const cached = this.cache.get(key)
    if (!cached) return null
    
    if (Date.now() - cached.timestamp > cached.cacheTime) {
      this.cache.delete(key)
      return null
    }
    
    return cached.data
  }

  setCache(key, data, cacheTime = this.cacheTime) {
    this.cache.set(key, {
      data,
      timestamp: Date.now(),
      cacheTime
    })
  }

  clearCache(url) {
    if (url) {
      const keys = Array.from(this.cache.keys())
      keys.forEach(key => {
        if (key.startsWith(url)) {
          this.cache.delete(key)
        }
      })
    } else {
      this.cache.clear()
    }
  }
}
  1. 请求队列管理:
class QueueHttpClient extends HttpClient {
  constructor(config = {}) {
    super(config)
    this.queue = []
    this.maxConcurrent = config.maxConcurrent || 5
    this.processing = 0
  }

  async request(config) {
    if (this.processing >= this.maxConcurrent) {
      await new Promise(resolve => {
        this.queue.push(resolve)
      })
    }
    
    this.processing++
    
    try {
      const response = await super.request(config)
      return response
    } finally {
      this.processing--
      if (this.queue.length > 0) {
        const next = this.queue.shift()
        next()
      }
    }
  }
}
  1. 使用示例:
// 创建实例
const http = new CacheHttpClient({
  baseURL: 'https://api.example.com',
  timeout: 30000,
  retryTimes: 3,
  maxConcurrent: 5
})

// API封装示例
const userApi = {
  async getUserInfo(userId) {
    return http.get(`/user/${userId}`, {}, { useCache: true })
  },
  
  async updateUser(userId, data) {
    return http.post(`/user/${userId}`, data)
  },
  
  async uploadAvatar(file) {
    const formData = new FormData()
    formData.append('file', file)
    
    return http.post('/user/avatar', formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
  }
}

// 使用示例
try {
  const userInfo = await userApi.getUserInfo(123)
  console.log(userInfo)
} catch (error) {
  console.error('Failed to get user info:', error)
}
  1. 最佳实践和注意事项:
  • 统一状态码和错误处理
  • 请求/响应数据转换
  • 取消请求处理
  • 防重复请求
  • 请求超时处理
  • 刷新token机制
  • 日志记录
  • 性能监控
  • 请求队列管理
  • 缓存策略
  • 重试机制
  • 并发控制

通过这样的封装,可以实现:

  1. 统一的接口规范
  2. 统一的错误处理
  3. 更好的代码复用
  4. 更容易的维护和扩展
  5. 更好的性能优化
  6. 更好的用户体验