Axios 如何取消重复请求
重复请求是如何产生的呢?这里我们举 2 个常见的场景:
- 假设页面中有一个按钮,用户点击按钮后会发起一个 AJAX 请求。如果未对该按钮进行控制,当用户快速点击按钮时,则会发出重复请求(备注:此场景防抖方法更适用,因为还是会发到后端,只是浏览器不会处理)
- 假设vue页面加载时会调一次接口,监听到某个值会调一次相同接口,第一次调的接口返回比第二次慢,导致最后获取到的数据是第一次接口返回的数据,
注:一般可以再加个白名单,允许重复请求,此方法还是会发到后端,只是浏览器不会处理
http.ts 文件
import axios, { AxiosRequestConfig } from 'axios'
import { Toast, Notify, Dialog } from 'vant'
import router from '@/router/index'
interface BaseResponse<T = any> {
code: number
data: T
message: string
}
const instance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API
})
//根据当前请求的信息,生成请求 Key
const generateReqKey = (config: AxiosRequestConfig) => {
const { method, url, params, data } = config
//通过url和method双重校验
return [method, url].join('&')
}
//把当前请求信息添加到pendingRequest对象中
const pendingRequest = new Map()
const addPendingRequest = (config: AxiosRequestConfig) => {
const requestKey = generateReqKey(config)
config.cancelToken =
config.cancelToken ||
new axios.CancelToken(cancel => {
if (!pendingRequest.has(requestKey)) {
pendingRequest.set(requestKey, cancel)
}
})
}
//检查是否存在重复请求,若存在则取消已发的请求
const removePendingRequest = (config: AxiosRequestConfig) => {
const requestKey = generateReqKey(config)
if (pendingRequest.has(requestKey)) {
const cancelToken = pendingRequest.get(requestKey)
cancelToken(requestKey)
pendingRequest.delete(requestKey)
}
}
instance.interceptors.request.use(
(config: AxiosRequestConfig) => {
//断网提示
if (!navigator.onLine) {
Notify({ type: 'warning', message: '您的网络故障,请检查!' })
}
if (localStorage.getItem('token')) {
config.headers.token = localStorage.getItem('token')
}
removePendingRequest(config) // 检查是否存在重复请求,若存在则取消已发的请求
addPendingRequest(config) // 把当前请求信息添加到pendingRequest对象中
return config
},
error => {
// console.log('请求拦截error', error)
return Promise.reject(error)
}
)
// interface TipsMessage {
// [propName: number]: string
// }
// const tipsMessage: TipsMessage = {
// 100: '登录失败 用户名密码不对或账号被锁定',
// 101: 'TOKEN失效 Token已失效,请重新调登录接口',
// 102: '参数错误 请根据接口参数要求进行传参',
// 103: '未登录',
// 300: '接口无访问权限 没有权限访问该接口',
// 301: '数据无访问权限 没有权限访问该设备数据',
// 302: '服务到期 设备服务到期 '
// }
instance.interceptors.response.use(
response => {
// setTimeout(() => {
// hideLoading()
// }, 200)
// console.log(response)
removePendingRequest(response.config) // 从pendingRequest对象中移除请求
const { code, message } = response.data
if (code !== 0 && code !== 200) {
if (code === 1011008 || code === 103 || code === 1011006) {
Dialog.alert({
title: '温馨提示',
message: '当前用户没有登录或登录失效,需要重新登录'
}).then(() => {
localStorage.clear()
// router.push({ name: 'Login' })
// store.commit('logout')
location.reload() // 为了重新实例化vue-router对象 避免bug
})
} else if (code === 20330033) {
// 未设置支付密码
router.push({
name: 'SetPayPassword'
})
} else {
Toast.fail(message)
// Toast.fail(`${tipsMessage[code]}`)
}
return Promise.reject(message)
} else {
return response
}
},
error => {
// setTimeout(() => {
// hideLoading()
// }, 200)
// console.log('http response - err', error)
removePendingRequest(error.config || {}) // 从pendingRequest对象中移除请求
if (axios.isCancel(error)) {
// console.log('已取消的重复请求:' + error.message)
} else {
// 添加异常处理
Toast.fail('服务器异常,请稍后再试')
}
return Promise.reject(error)
}
)
const request = <T = any>(config: AxiosRequestConfig): Promise<T> => {
return new Promise((resolve, reject) => {
instance
.request<BaseResponse<T>>(Object.assign(config))
.then(res => resolve(res.data.data))
.catch(err => reject(err))
})
}
export default request