取消重复请求依据
当多个请求的
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()
封装后的代码
// 拦截重复请求
import type { AxiosRequestConfig } from 'axios'
// 从 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, params, data } = config
return [url || '', method || '', queryString(params || {}), queryString(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)
}
}
}
// 序列化对象,推荐用qs库,这里就简单的手写吧
interface Obj {
[name: string]: number | null | string | undefined | boolean
}
function queryString(obj: Obj): string {
const params = new URLSearchParams()
// 可能是对象或者stringify后的对象
if (typeof obj === 'string') {
obj = JSON.parse(obj) as Obj
}
Object.keys(obj).forEach(key => {
params.append(key, String(obj[key]))
})
return params.toString()
}
使用
import axios from 'axios'
import { history } from 'umi'
import { RequestCanceler } from './canceler.ts'
const canceler = new RequestCanceler()
const api = axios.create({
// baseURL: '/api',
baseURL: '/mock/api',
withCredentials: true
// timeout: 500
})
// 请求拦截
api.interceptors.request.use((config) => {
let token = localStorage.getItem('sid')
if (token) {
// 设置请求头
config.headers[ 'Authorization' ] = 'Bearer ' + token
}
if (config.download) {
// download设置responseType
config.responseType = 'blob'
}
// 检查是否存在重复请求,若存在则取消已发的请求
canceler.removePendingRequest(config)
// 把当前的请求信息添加到pendingRequest
canceler.addPendingRequest(config)
return config
},
(error) => Promise.reject(error))
// 返回拦截
api.interceptors.response.use((response) => {
if (response.status === 200) {
if (response.config.download) {
// download
// 转换文件下载路径
let objectUrl = URL.createObjectURL(new Blob([response.data]))
// 获取文件名
let fileName = decodeURI(response.headers[ 'content-disposition' ].split('=')[ 1 ])
const link = document.createElement('a')
link.download = fileName
link.href = objectUrl
link.click()
canceler.removePendingRequest(response.config)
return Promise.resolve(response)
}
canceler.removePendingRequest(response.config)
return Promise.resolve(response.data)
} else {
canceler.removePendingRequest(response.config)
return Promise.reject(response)
}
},
(error) => {
canceler.removePendingRequest(error.config || {})
if (error.response.status === 401) {
history.push('/?overtime=true')
}
return Promise.reject(error.response)
})
export default api
总结:亲测有效,放心食用