原理
通过 Aop
方式对 axios
的 adapter
进行拦截,把相同的请求除了第一个正常发起,其他重复的请求进行拦截挂起,等到第一个请求成功/失败之后再把结果返回给所有的重复请求。
axios配置
拦截规则
url
、请求方式
、请求参数
一致,则认为是重复请求。
实现
注意:
本文使用的是最原始的 axios
进行创建的实例,如果对 axios
进行了二次封装的,请将 Axios.create(baseConfig)
替换为自己对应的创建 axios
实例的方法
import Axios from 'axios'
/**
* @typedef {import('axios').AxiosRequestConfig} AxiosRequestConfig
* @typedef {import('axios').AxiosInstance} AxiosInstance
*/
/* 缓存 */
const useCache = () => {
const cacheMap = new Map()
const addCache = (key, value) => cacheMap.set(key, value)
const deleteCache = key => cacheMap.delete(key)
const getCache = key => cacheMap.get(key)
const hasCache = key => cacheMap.has(key)
return {
addCache,
deleteCache,
getCache,
hasCache,
}
}
/**
* key规则
* @param { AxiosRequestConfig } config
* @return {`__${string}__${string}`}
*/
const getKeyByConfig = config => {
const { url, data, method, params } = config
// 根据请求方法选择参数
const getStringParams = () => {
try {
if (method.toLowerCase() === 'get') {
return encodeURIComponent(JSON.stringify(params))
} else {
return encodeURIComponent(JSON.stringify(data))
}
} catch (e) {
console.error('params or data is not json format!', e)
return ''
}
}
return `__${method}__${encodeURIComponent(url)}__${getStringParams()}`
}
/**
* 将filter进行标准化处理。
* @param filter
* @return {*|(function(*): (boolean|*))}
*/
const normalizeFilter = filter => {
if (typeof filter === 'function') {
return filter
}
return config => {
if (!filter || (Array.isArray(filter) && !filter.length)) {
return true
}
const { url } = config
// 判断是否为正则
if (filter instanceof RegExp) {
return !filter.test(url)
} else {
const filterUrls = Array.isArray(filter) ? filter : [filter]
return !filterUrls.includes(url)
}
}
}
/**
* 接口拦截过滤
* @param { AxiosRequestConfig } config 这个是每个接口的配置
* @param { AxiosRequestConfig | undefined } baseConfig 这是基本配置
*/
const filterIntercept = (config, baseConfig) => {
const { url, params, data } = config
const { deduplicateFilter } = baseConfig ?? {}
/* 没有url不处理 TODO: 对于 formData 暂不处理 */
if (!url || params instanceof FormData || data instanceof FormData) {
return false
}
if (deduplicateFilter) {
return normalizeFilter(deduplicateFilter)(config)
}
/* other filter */
return true
}
/**
* 用于处理重复请求的适配器。对于重复请求会被挂起,直到第一个请求成功/失败后,会把结果给到其他重复请求。
* @param { AxiosInstance } axiosInstance
* @param { AxiosRequestConfig | undefined } baseConfig
*/
const deduplicateRequestsAdapter = (axiosInstance, baseConfig) => {
const { addCache, deleteCache, getCache, hasCache } = useCache()
/* 默认的适配器。用于调用原始的请求 */
const defaultAdapter = axiosInstance.defaults.adapter
if (!defaultAdapter) {
return axiosInstance
}
/**
* 通过aop方式修改默认适配器,进行重复请求拦截
* @param { AxiosRequestConfig } config
* @return {Promise<any>}
*/
axiosInstance.defaults.adapter = async config => {
if (!filterIntercept(config, baseConfig) || config.notDeduplicate) {
return defaultAdapter(config)
}
const key = getKeyByConfig(config)
/* 缓存存在则挂起 */
if (hasCache(key)) {
return new Promise((resolve, reject) => getCache(key).push({ resolve, reject }))
} else {
addCache(key, [])
}
let request = void 0
try {
request = await defaultAdapter(config)
getCache(key).forEach(it => it.resolve(request))
} catch (e) {
/* TODO:针对错误是否进行重试 */
getCache(key).forEach(it => it.reject(e))
} finally {
deleteCache(key)
}
return request
}
return axiosInstance
}
/**
* 创建一个拦截重复请求的axios实例
* @param { AxiosRequestConfig | undefined } baseConfig
*/
export const createDuplicateRequestAxios = baseConfig => {
return deduplicateRequestsAdapter(Axios.create(baseConfig), baseConfig)
}
使用
const instance = createDuplicateRequestAxios()
instance
.get('/api/resource', {
params: {
search: 'keyword',
page: 2,
},
})
.catch(res => {
console.log(res, '111')
})
instance
.get('/api/resource', {
params: {
search: 'keyword',
page: 2,
},
})
.catch(res => {
console.log(res, '222')
})
instance
.get('/api/resource', {
params: {
search: 'keyword',
page: 2,
},
})
.catch(res => {
console.log(res, '333')
})
instance
.post('/api/resource', {
data: {
search: 'keyword',
page: 2,
},
})
.catch(res => {
console.log(res, '444')
})
instance
.post('/api/resource', {
data: {
search: 'keyword',
page: 2,
},
})
.catch(res => {
console.log(res, '555')
})
instance
.post('/api/resource', {
data: {
search: 'keyword',
page: 2,
},
})
.catch(res => {
console.log(res, '666')
})
结果
一、可以看到请求只发起了两次
二、结果输出了 6
次。(因为有 6个请求
)
如果想要部分或者某个接口不配置拦截重复请求
一、全局不使用
const instance = createDuplicateRequestAxios({ notDeduplicate: true })
二、部分请求不使用
const instance = createDuplicateRequestAxios({
// 拦截单个请求
deduplicateFilter: '/api/resource',
// 拦截多个请求
// deduplicateFilter: ['/api/resource', '/api/resource2', '/api/resource3'],
// 通过正则匹配
// deduplicateFilter: /^\api/,
})