目的
1. 当需要发起一个请求之前,可以先取消掉之前的请求,这样确保每次都获取到最新的响应数据,避免数据错乱。
2. 避免用户点击按钮触发了多个相同的网络请求。
AbortController
axios提供了两个取消请求的API,分别是CancelToken和AbortController。但是此 API 从 v0.22.0
开始已被弃用,不应在新项目中使用。
从 v0.22.0
开始,Axios 支持以 fetch API 方式—— AbortController 取消请求:
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// 取消请求
controller.abort()
实现思路
1. 如果在当前接口设置了不允许重复请求,那么在请求拦截器中设置AbortController,请求这个接口的时候,拼接接口的url和参数等信息保存到一个map当中
2. 再次请求该接口时,先判断map中是否存在同名key,如果存在,那么调用Abortcontroller的abort方法执行取消,反之则在map当中新增key
// 拼接请求的key
function getRequestKey(config: AxiosRequestConfig) {
return (config.method || '') + config.url + '?' + qs.stringify(config?.data)
}
function setPendingMap(config: AxiosRequestConfig) {
const controller = new AbortController()
config.signal = controller.signal
const key = getRequestKey(config)
if (pendingMap.has(key)) {
pendingMap.get(key).abort()
pendingMap.delete(key)
} else {
pendingMap.set(key, controller)
}
}
3. 在响应拦截器中,如果map中存在接口的key则删除。
完整代码
// 二次封装axios
import { ElMessage } from 'element-plus'
import useUserStore from '@/store/modules/user'
import qs from 'qs'
import axios, {
AxiosInstance,
AxiosError,
AxiosResponse,
AxiosRequestConfig,
} from 'axios'
declare module 'axios' {
export interface AxiosRequestConfig {
isReturnNativeData?: boolean
errorMode?: string
repeatRequest?: boolean
}
}
const pendingMap = new Map()
function getRequestKey(config: AxiosRequestConfig) {
return (config.method || '') + config.url + '?' + qs.stringify(config?.data)
}
function setPendingMap(config: AxiosRequestConfig) {
const controller = new AbortController()
config.signal = controller.signal
const key = getRequestKey(config)
if (pendingMap.has(key)) {
pendingMap.get(key).abort()
pendingMap.delete(key)
} else {
pendingMap.set(key, controller)
}
}
const request = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 5000,
})
request.interceptors.request.use(
(config) => {
const userStore = useUserStore()
if (userStore.token) {
config.headers.token = userStore.token
}
if (!config.repeatRequest) {
setPendingMap(config)
}
return config
},
(error) => {
return Promise.reject(error)
},
)
request.interceptors.response.use(
(response) => {
const config = response.config
const key = getRequestKey(config)
pendingMap.delete(key)
if (response.status === 200) {
return Promise.resolve(response.data)
} else {
return Promise.reject(response.data)
}
},
(error) => {
let message = ''
const status = error.response.status
switch (status) {
// 401: 未登录
// 未登录则跳转登录页面,并携带当前页面的路径
// 在登录成功后返回当前页面,这一步需要在登录页操作。
case 401:
message = '未登录'
break
// 403 token过期
// 登录过期对用户进行提示
// 清除本地token和清空vuex中token对象
// 跳转登录页面
case 403:
message = '登录过期,请重新登录'
break
case 404:
message = '网络请求不存在'
break
case 500:
message = '服务器出现问题'
break
default:
message = error.response.data.message
break
}
ElMessage({
type: 'error',
message,
})
return Promise.reject(error)
},
)
export default request
import request from '@/utils/request'
export default {
getDeptTree: (data: {}) => {
return request({
url: '/getDeptTree',
method: 'post',
data,
repeatRequest: false
})
}
}