Skip to Content
Nextra 4.0 is released 🎉
笔记WebAxios二次封装,集取消重复请求、超时重发等强大功能

Axios二次封装,集取消重复请求、超时重发等强大功能

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

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

项目结构

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

类型定义

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

type.ts

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 }

具体封装步骤

创建请求实体类

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

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

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() } ... }

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

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

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' }) } ... }

注册拦截器方法

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

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

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,坚持就是胜利!

错误状态码统一判断

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

checkErrorStatus.ts

/** * 对错误状态码进行检测 */ 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 函数,来对错误状态进行统一判断。

取消重复请求

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

AbortAxios.ts中:

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方法中补上下面标识的内容:

setInterceptors() { // 如果配置中并没有传入拦截器,则直接返回 if (!this.interceptors) return // 解构出各个拦截器 const { requestInterceptors, requestInterceptorsCatch, responseInterceptor, responseInterceptorsCatch } = this.interceptors // 创建取消请求实例 const abortAxios = new AbortAxios() // 挂载请求拦截器 this.axiosInstance.interceptors.request.use((config: InternalAxiosRequestConfig) => { // 是否清除重复请求 const abortRepetitiveRequest = (config as unknown as any)?.abortRepetitiveRequest ?? this.options.abortRepetitiveRequest if (abortRepetitiveRequest) { // 存储请求标识 abortAxios.addPending(config) } if (requestInterceptors) { // 如果存在请求拦截器,则将 config 先交给 requestInterceptors 做对应的配置。 config = requestInterceptors(config) } return config }, requestInterceptorsCatch ?? undefined) // 挂载响应拦截器 this.axiosInstance.interceptors.response.use((res: AxiosResponse) => { // 取消请求 res && abortAxios.removePending(res.config) 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 }) }

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

超时报错重发

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

axiosRetry.ts 中:

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)) }

实现拦截器

index.ts中:

// 继承了我们在最开始实现的抽象类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) }, }

创建实例

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

const useRequest = new AxiosMax({ directlyGetData: true, baseURL: staticAxiosConfig.baseUrl, timeout: 3000, interceptors: _RequstInterceptors, abortRepetitiveRequest: true, retryConfig: { count: 5, waitTime: 500 } }) export default useRequest
Last updated on