当多个请求的method
(get,post,put等),url
,params
和data
完全相同时,我们可以认为他们是相同请求。而当后端还没有返回数据,此时又发起了相同的请求,就可以对该请求进行拦截。
封装思路:
-
根据当前请求生成KEY。可以使用
qs
库或者手写一个queryString,来生成一个requestKey标注这些请求。(对应下面代码中的generateReqKey方法) -
如果该请求的requestKey在
pendingRequest
map中不存在,则新创建一个controller对象(new AbortController),并将controller的signal属性挂载到请求的config属性中。否则就获取之前的controller,并挂载到当前请求的config属性上。(对应addPendingRequest
方法)挂载singal的目的是为了之后能够取消请求。注意,相同的requestKey(相同的请求)要挂载相同的singal方便后面统一取消重复请求。
const controller = new AbortController(); // controller具有一个方法 `abort()`和一个属性 `signal`
-
检查是否存在重复请求,若存在则取消已发请求。(对应
removePendingRequest
方法)controller.abort()
封装的代码
在这里,我们使用qs
序列化对象。
npm i qs
npm i @types/qs
// 拦截重复请求
import type { AxiosRequestConfig } from 'axios'
import qs from 'qs'
// 从 v0.22.0 开始,Axios 支持以 fetch API 方式—— AbortController 取消请求:
// CancelToken 已经被淘汰了
export class RequestCanceler {
// 存储每个请求的标志和取消函数
pendingRequest: Map<string, AbortController>
constructor() {
this.pendingRequest = new Map<string, AbortController>()
}
generateReqKey(config: AxiosRequestConfig): string {
const { method, url } = config
return [url || '', method || '', qs.stringify(config.params), qs.stringify(config.data)].join('&')
}
addPendingRequest(config: AxiosRequestConfig) {
const requestKey: string = this.generateReqKey(config)
if (!this.pendingRequest.has(requestKey)) {
const controller = new AbortController()
// 给config挂载signal
config.signal = controller.signal
this.pendingRequest.set(requestKey, controller)
} else {
// 如果requestKey已经存在,则获取之前设置的controller,并挂载signal
config.signal = (this.pendingRequest.get(requestKey) as AbortController).signal
}
}
removePendingRequest(config: AxiosRequestConfig) {
const requestKey = this.generateReqKey(config)
if (this.pendingRequest.has(requestKey)) {
// 取消请求
(this.pendingRequest.get(requestKey) as AbortController).abort()
// 从pendingRequest中删掉
this.pendingRequest.delete(requestKey)
}
}
}
如何使用
封装好了,就可以开始使用啦:
import axios from 'axios'
import { RequestCanceler } from './helper/requestCanceler'
const canceler = new RequestCanceler()
// 设置全局超时 2min
axios.defaults.timeout = 120000
// 创建axios实例
const service = axios.create({
headers: {
'Content-Type': 'application/json',
},
})
// 添加请求拦截器
service.interceptors.request.use(
function (config) {
// 检查是否存在重复请求,若存在则取消已发的请求
canceler.removePendingRequest(config)
// 把当前的请求信息添加到pendingRequest
canceler.addPendingRequest(config)
return config
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error)
}
)
// 添加响应拦截器
service.interceptors.response.use(
function (response: AxiosResponse) {
canceler.removePendingRequest(response.config)
return response.data
},
function (error: AxiosError) {
canceler.removePendingRequest(error.config || {})
const { response } = error
if (axios.isCancel(error)) {
return {
success: false,
message: 'repeat request',
}
}
return Promise.reject(error)
}
)
以上,就对axios进行了最基础的重复请求拦截的封装。
加载动画
因为我们已经对每个请求序列化生成了key:所以我们可以在每个请求前,开始加载动画,请求完后,关闭加载动画。这里我使用的是antdesign 5.0。
import { message } from 'antd'
// 开始加载动画
message.loading({
content: '加载中...',
key: requestKey,
duration:0,设为 0 时不自动关闭
})
// 关闭加载动画
message.destroy(requestKey)
// 拦截重复请求
import type { AxiosRequestConfig } from 'axios'
import { message } from 'antd'
import qs from 'qs'
export class RequestCanceler {
// 存储每个请求的标志和取消函数
pendingRequest: Map<string, AbortController>
constructor() {
this.pendingRequest = new Map<string, AbortController>()
}
generateReqKey(config: AxiosRequestConfig): string {
const { method, url } = config
return [url || '', method || '', qs.stringify(config.params), qs.stringify(config.data)].join('&')
}
addPendingRequest(config: AxiosRequestConfig) {
const requestKey: string = this.generateReqKey(config)
message.loading({
content: '加载中...',
key: requestKey,
})
if (!this.pendingRequest.has(requestKey)) {
const controller = new AbortController()
// 给config挂载signal
config.signal = controller.signal
this.pendingRequest.set(requestKey, controller)
} else {
// 如果requestKey已经存在,则获取之前设置的controller,并挂载signal
config.signal = (this.pendingRequest.get(requestKey) as AbortController).signal
}
}
removePendingRequest(config: AxiosRequestConfig) {
const requestKey = this.generateReqKey(config)
message.destroy(requestKey)
if (this.pendingRequest.has(requestKey)) {
// 取消请求
;(this.pendingRequest.get(requestKey) as AbortController).abort()
// 从pendingRequest中删掉
this.pendingRequest.delete(requestKey)
}
}
}