谁都能学会的axios二次封装,集成【取消重复请求、超时重发】等强大功能

一、为什么要对axios封装?多此一举?

本文将基于 typescript 对 axios 进行优雅封装,同时会将每部分内容细化,尽最大努力带大家弄明白每一处知识和细节。

其实要对 axios 进行封装并不是一个简单的过程,也有许多人认为这是化简为繁,但其实封装本身就是麻烦自己,方便所有人。对 axios 进行二次封装有助于使项目的网络请求方式更加规范化,可复用性和定制化程度更高,减少重复劳动。

"如何对 axios 进行封装"、"前端短时间内发送多个http请求,如何确保获取最后发送请求的响应?"这些是我们在面试中常聊到的话题,相信跟着本文来一点点敲出属于自己的 AxiosMax,在回答这些问题的时候也能更随心应手。

本文基于 vue-vben-admin 项目对 axios 的封装方式,全文代码仓库在文末。

二、项目结构

  • AbortAxios.ts: 取消请求实体类
  • Axios.ts: 请求实体类
  • axiosRetry.ts: 重复请求方法
  • checkErrorStatus:错误状态码处理
  • config.ts:静态配置
  • index.ts:实例创建、拦截器实现
  • type.ts:类型定义

三、类型定义

如果项目并没有使用 ts 的需求,或者还不了解 ts 的,也可以直接采用 js 进行封装,把相关类型删除就行。

type.ts

typescript 复制代码
import type { AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse, AxiosInstance, AxiosError } from 'axios'

/**
 *  axios实例配置选项,继承AxiosRequestConfig
 */
export interface AxiosOptions extends AxiosRequestConfig {
  // 是否直接返回data数据
  directlyGetData?: boolean
  // 定义拦截器
  interceptors?: RequstInterceptors
  // 是否取消重复请求
  abortRepetitiveRequest?: boolean
  // 重连配置
  retryConfig?: {
    // 重连次数
    count: number
    // 每次请求间隔时间
    waitTime: number
  }
}

/**
 *  定义拦截器抽象类,后续在index.ts文件中继承实现
 */
export abstract class RequstInterceptors {
  // 请求拦截器
  abstract requestInterceptors?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
  // 请求错误拦截器
  abstract requestInterceptorsCatch: (err: Error) => Error
  // 响应拦截器
  abstract responseInterceptor?: (res: AxiosResponse) => AxiosResponse
  // 响应错误拦截器
  abstract responseInterceptorsCatch?: (axiosInstance: AxiosInstance, error: AxiosError) => void;
}

/**
 *  定义返回类型 
 */
export interface Respones<T = any> {
  code: number
  result: T
}

四. 具体封装步骤

1. 创建请求实体类

Axios.ts文件中,创建一个实体类 AxiosMax

后续可通过 const useRequest = new AxiosMax(...)直接使用。

typescript 复制代码
class AxiosMax {
  // axios实例, 通过axios.create()方法创建
  private axiosInstance: AxiosInstance
  // 传入的配置
  private options: AxiosOptions
  // 拦截器
  private interceptors: RequstInterceptors | undefined
  constructor(options: AxiosOptions) {
    this.axiosInstance = axios.create(options)
    this.options = options
    this.interceptors = options.interceptors
    // 对拦截器进行初始化注册
    this.setInterceptors()
  }
  ...
}

2. 在 AxiosMax 实体类中创建统一请求方法

后续通过 ueRequest.get({ url: '/a' })直接调用

typescript 复制代码
class AxiosMax {
    
    ...
    
  /**
   * 统一请求方法
   */
  request<T = any>(config: AxiosRequestConfig): Promise<T> {
    return new Promise((resolve, reject) => {
      this.axiosInstance.request<any, AxiosResponse<Respones>>(config).then((res) => {
        return resolve(res as unknown as Promise<T>)
      }).catch((err) => {
        return reject(err)
      })
    })
  }

  get<T = any>(config: AxiosRequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'GET' })
  }

  post<T = any>(config: AxiosRequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'POST' })
  }

  put<T = any>(config: AxiosRequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'PUT' })
  }

  delete<T = any>(config: AxiosRequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'DELETE' })
  }
  
 	... 
 
}

3. 注册拦截器方法

这部分的内容相对于较多,但是关键知识较多,希望大家能够耐心看完。

本小节主要是讲述创建注册拦截器的方法。

typescript 复制代码
class AxiosMax {
    
    ...
    
  /**
   * 注册拦截器方法
   */
  setInterceptors() {
    // 如果配置中并没有传入拦截器,则直接返回
    if (!this.interceptors) return
	
    // 解构出各个拦截器
    const {
      requestInterceptors,
      requestInterceptorsCatch,
      responseInterceptor,
      responseInterceptorsCatch
    } = this.interceptors

    // 挂载请求拦截器
    this.axiosInstance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
      if (requestInterceptors) {
        // 如果存在请求拦截器,则将 config 先交给 requestInterceptors 做对应的配置。
        config = requestInterceptors(config)
      }
      return config
    }, requestInterceptorsCatch ?? undefined)

    // 挂载响应拦截器
    this.axiosInstance.interceptors.response.use((res: AxiosResponse) => {
        
      if (responseInterceptor) {
        // 如果存在响应拦截器,则将返回值先交给 responseInterceptor 做处理
        res = responseInterceptor(res)
      }

      // 根据 options.directlyGetData 配置选项判断是否直接取得data值 
      if (this.options.directlyGetData) {
        res = res.data
      }
      return res
    }, (err: AxiosError) => {
      if (responseInterceptorsCatch) {
        // 如果存在响应错误拦截器,则将返回值交给 responseInterceptorsCatch 做处理
        return responseInterceptorsCatch(this.axiosInstance, err)
      }
      return err
    })
  }

	...
    
}

内容到这里,我们已经实现了 axios 二次封装的雏形,直接拿去使用也不是不行,但总感觉还是缺了点什么,下面就将带大家继续完善我们的 AxiosMax,坚持就是胜利!

4. 错误状态码统一判断

经历了上面复杂的内容,我们来点简单的放松一下!

checkErrorStatus.ts 中:

typescript 复制代码
/**
 * 对错误状态码进行检测
 */
export function checkErrorStatus(status: number | undefined, message: string | undefined, callback: (errorMessage: string) => any) {
  let errorMessage = message ?? ''
  switch (status) {
    case 400:
      errorMessage = '客户端错误,请求格式或参数有误!'
      break
    case 401:
      errorMessage = '身份认证不通过'
      break
    case 403:
      errorMessage = '用户得到授权,但是访问是被禁止的!'
      break
    case 404:
      errorMessage = '未找到目标资源!'
      break
    case 500:
      errorMessage = '服务器错误!'
      break
    case 503:
      errorMessage = '服务器错误!'
      break
  }
  if (errorMessage.length > 0) {
    callback(`checkErrorStatus:${errorMessage}`)
  }
}

后续将在响应错误拦截器当中调用 checkErrorStatus 函数,来对错误状态进行统一判断。

5. 取消重复请求

"前端短时间内发送多个http请求,如何确保获取最后发送请求的响应?"这是面试官常问的一道题,接下来,我就将带大家实现一遍相似功能,其核心是通过 AbortController API来取消请求。

AbortAxios.ts中:

typescript 复制代码
import { AxiosRequestConfig } from "axios"

// 用于存储控制器
const pendingMap = new Map<string, AbortController>()

// 创建各请求唯一标识, 返回值类似:'/api:get',后续作为pendingMap的key
const getUrl = (config: AxiosRequestConfig) => {
  return [config.url, config.method].join(':')
}

class AbortAxios {
  // 添加控制器
  addPending(config: AxiosRequestConfig) {
    this.removePending(config)
    const url = getUrl(config)
    // 创建控制器实例
    const abortController = new AbortController()
    // 定义对应signal标识
    config.signal = abortController.signal
    if (!pendingMap.has(url)) {
      pendingMap.set(url, abortController)
    }
  }
  // 清除重复请求
  removePending(config: AxiosRequestConfig) {
    const url = getUrl(config)
    if (pendingMap.has(url)) {
      // 获取对应请求的控制器实例
      const abortController = pendingMap.get(url)
      // 取消请求
      abortController?.abort()
      // 清除出pendingMap
      pendingMap.delete(url)
    }
  }
}

export default AbortAxios

AxiosMax类的 setInterceptors方法中补上下面标识的内容:

至此,我们的注册拦截器函数才完成,我们也实现了取消重复请求的功能。

6. 超时报错重发

本小节将带大家实现请求超时报错重发的功能。

axiosRetry.ts 中:

typescript 复制代码
import type { AxiosError, AxiosInstance } from "axios";

export function retry(instance: AxiosInstance, err: AxiosError) {
  const config: any = err.config
  // 获取配置项内容(请求间隔时间,请求次数)
  const { waitTime, count } = config.retryConfig ?? {}
  // 当前重复请求的次数
  config.currentCount = config.currentCount ?? 0
  console.log(`第${config.currentCount}次重连`)

  // 如果当前的重复请求次数已经大于规定次数,则返回Promise
  if (config.currentCount >= count) {
    return Promise.reject(err)
  }
  config.currentCount++
  
  // 等待间隔时间结束后再执行请求
  return wait(waitTime).then(() => instance(config))
}

function wait(waitTime: number) {
  return new Promise(resolve => setTimeout(resolve, waitTime))
}

我们将在下一小节中展示 retry 函数的具体用法。

7. 实现拦截器

index.ts中:

typescript 复制代码
// 继承了我们在最开始实现的抽象类RequstInterceptors,主要关心responseInterceptorsCatch内容
const _RequstInterceptors: RequstInterceptors = {
  requestInterceptors(config) {
    return config
  },
  requestInterceptorsCatch(err) {
    return err
  },
  responseInterceptor(config) {
    return config
  },
  responseInterceptorsCatch(axiosInstance, err: AxiosError) {
    let message = err.code === 'ECONNABORTED' ? '请求超时' : undefined
    // 判断本次请求是否已经被取消
    if (axios.isCancel(err)) {
      return Promise.reject(err);
    }
    // 检查响应状态码
    checkErrorStatus((err as AxiosError).response?.status, message,  (message) => console.log(message))
    
    // 响应错误实现重连功能
    return retry(axiosInstance, err as AxiosError)
  },
}

8. 创建实例

这是我们的最后一步,就是创建 AxiosMax 的实例,然后就可以尽情使用由我们自己封装的axios了。

typescript 复制代码
const useRequest = new AxiosMax({
  directlyGetData: true,
  baseURL: staticAxiosConfig.baseUrl,
  timeout: 3000,
  interceptors: _RequstInterceptors,
  abortRepetitiveRequest: true,
  retryConfig: {
    count: 5,
    waitTime: 500
  }
})

export default useRequest

9. 测试

通过简单的测试,封装的 axios 已经实现了我们既定的功能。

代码仓库地址:github.com/windlil/axi...

感谢大家的阅读!

相关推荐
PAK向日葵2 小时前
【算法导论】PDD 0817笔试题题解
算法·面试
加班是不可能的,除非双倍日工资4 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi4 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip5 小时前
vite和webpack打包结构控制
前端·javascript
excel5 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国5 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼5 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy5 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT6 小时前
promise & async await总结
前端
Jerry说前后端6 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化