Axios二次封装,集取消重复请求、超时重发等强大功能
为什么要对axios封装?多此一举?
其实要对 axios 进行封装并不是一个简单的过程,也有许多人认为这是化简为繁,但其实封装本身就是麻烦自己,方便所有人。对 axios 进行二次封装有助于使项目的网络请求方式更加规范化,可复用性和定制化程度更高,减少重复劳动。
项目结构
-- http/
|--- AbortAxios.ts
|--- Axios.ts
|--- axiosRetry.ts
|--- checkErrorStatus.ts
|--- config.ts
|--- index.ts
|--- type.tsAbortAxios.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