在实际项目开发过程中,我们可能会遇到这么一种情况,假设现在有一个tab,tab下对应一个表格,切换tab时候请求下面的表格对应数据,假设tab>a页签点击后未等数据返回用户切换到了tab>b页签下,这时候很可能造成下面表格渲染错误(因为ajax请求是异步的,很有可能上面一个接口请求到的数据最终渲染到当前页签下),面对这个问题,就需要用到axios取消请求了
关于axios取消请求目前网上流传的方法主要都是基于老版本CancelToken去实现的参考,此 API 从 v0.22.0 开始已被弃用,不应在新项目中使用,这里不再详谈。从v0.22.0开始,Axios 支持以 fetch API 方式 — AbortController取消请求,官方示例:
1 2 3 4 5 6 7 8 9 | const controller = new AbortController(); axios.get('/foo/bar', { signal: controller.signal }).then(function(response) { //... }); // 取消请求 controller.abort() |
单纯看上面官方demo就是看个寂寞,上面方式阻止了所有请求,无法满足我们项目实际需求,实际开发中我们还需要进行额外的封装,我们要达到的目的是当用户频繁请求的过程中,当有新的请求时候把前面还没返回的请求中止掉,只保留最后一次请求,确保最终页面数据渲染正确
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | import type { AxiosRequestConfig } from 'axios'; import axios from 'axios'; import { ElMessage } from 'element-plus'; import { globalStore } from '@/store'; const GLOBAL_STORE = globalStore(); const { requests } = storeToRefs(GLOBAL_STORE); const reload = () => { if (window.parent && window.parent !== window.self) { window.parent.location?.reload(); } }; const blob2json = (data: Blob): Promise<Record<string, any>> => new Promise((resolve) => { const reader = new FileReader(); reader.readAsText(data, 'utf-8'); reader.onload = function () { const res = JSON.parse(reader.result as string); resolve(res); }; }); // 处理接口内层 code const resolveData = (data: any) => { if (data.code && data.code !== '200') { const msg = data.entity || '接口返回异常'; ElMessage.error(msg); return Promise.reject(msg); } return data; }; const service = axios.create({ // baseURL: import.meta.env.VITE_APP_BASE_API as string, withCredentials: true, timeout: 100000, headers: { 'Content-Type': 'application/json;charset=utf-8' }, }); service.interceptors.request.use( (config: AxiosRequestConfig) => { const controller = new AbortController(); // 每个请求时都新生成一个AbortController实例 config.signal = controller.signal; // 设置请求的signal字段为new AbortController()的signal GLOBAL_STORE.$patch((state) => { // requests:请求列表存到状态管理pania的state中 state.requests.push(controller); }); const token = localStorage.getItem('UTOKEN'); if (token && config.headers) { config.headers['Authorization'] = token; } return config; }, (error) => Promise.reject(error) ); service.interceptors.response.use( async (response) => { console.log('requests.value', requests.value); requests.value.slice(0, -1).forEach((controller) => controller.abort()); // 通过遍历终止所有未完成的请求 GLOBAL_STORE.$patch((state) => { state.requests = requests.value.slice(-1); // 执行完清空,从而不影响新页面的请求列表 }); const res: any = response.data; let { code } = res; if (response.request.responseType === 'blob') { const _res = await blob2json(res); if (_res && _res.code) { code = _res.code; } } if (code === '2002') { ElMessage.error('登录过期,请重新登录'); reload(); } else if (code !== '1001' && !Array.isArray(res)) { const msg = res.msg || '接口请求出错'; ElMessage.error(msg); return Promise.reject(msg); } else { ElMessage({ message: '请求成功', type: 'success', }); return resolveData(res.data || res); } }, (error) => { const msg = error && error.message ? error.message : error; ElMessage.error(msg); if (axios.isCancel(error)) { //中止后的请求会走这里 console.error('请求被取消', error); } return Promise.reject('错误信息' + msg); } ); export default service; |
上面方式虽然简单,但又显得过于粗暴,因为它每次有新的请求就会把前面没请求完成的全部中止,所以每次并发请求池中只能有最后一个请求成功,事实上我们进入一个页面请求可能多则数十个,这个时候上面方式肯定是不行的。因此我们还需要把每条接口存储起来
例如:进入页面后请求a、b、c、d四个接口,同时并发,于是我们将所有的接口以及状态对应存储,这时候就通过map方式无疑是最好的选择,当存储好了后,例如后面有新的请求a,我们会去找map中存储的状态值,如果发现前面的接口a还没请求完成,就把它中止,请求我们本次最新发出的请求a,从而完美解决上面问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | // 新建abortController.js封装文件 export default class abortController { // 声明一个 Map 用于存储每个请求的标识 和 取消函数 static pending = new Map(); // 白名单, 写入接口名称 static whiteRequest = []; /** * 得到该格式的url * @param {AxiosRequestConfig} config * @returns */ static getUrl(config) { return [config.method, config.url].join('&'); } /** * 添加请求 * @param {AxiosRequestConfig} config */ static addPending(config) { const url = this.getUrl(config); const controller = new AbortController(); // 每个请求时都新生成一个AbortController实例 config.signal = controller.signal; // 设置请求的signal字段为new AbortController()的signal if (!this.pending.has(url)) { // 如果 pending 中不存在当前请求,则添加进去 this.pending.set(url, controller); } console.log('添加请求', this.pending); } /** * 移除请求 * @param {AxiosRequestConfig} config */ static removePending(config) { const url = this.getUrl(config); const method = url.split('&')[1]; if (this.pending.has(url) && !this.whiteRequest.includes(method)) { // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除 const controller = this.pending.get(url); controller.abort(); // 取消请求 this.pending.delete(url); } } /** * 清空 pending 中的请求(在路由跳转时调用) */ static clearPending() { for (const [url, controller] of this.pending) { controller.abort(); // 取消请求 } this.pending.clear(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | import type { AxiosRequestConfig } from 'axios'; import axios from 'axios'; import abortController from './abortController'; import { ElMessage } from 'element-plus'; const reload = () => { if (window.parent && window.parent !== window.self) { window.parent.location?.reload(); } }; const blob2json = (data: Blob): Promise<Record<string, any>> => new Promise((resolve) => { const reader = new FileReader(); reader.readAsText(data, 'utf-8'); reader.onload = function () { const res = JSON.parse(reader.result as string); resolve(res); }; }); // 处理接口内层 code const resolveData = (data: any) => { if (data.code && data.code !== '200') { const msg = data.entity || '接口返回异常'; ElMessage.error(msg); return Promise.reject(msg); } return data; }; const service = axios.create({ // baseURL: import.meta.env.VITE_APP_BASE_API as string, withCredentials: true, timeout: 100000, headers: { 'Content-Type': 'application/json;charset=utf-8' }, }); service.interceptors.request.use( (config: AxiosRequestConfig) => { // 这里的判断用于处理白名单不参与取消请求 if (!abortController.whiteRequest.includes(`${config.url}`)) { // 请求开始前,检查一下是否已经有该请求了,有则取消掉该请求 abortController.removePending(config); // 把当前请求添加进去 abortController.addPending(config); } console.log('config', config); const token = localStorage.getItem('UTOKEN'); if (token && config.headers) { config.headers['Authorization'] = token; } return config; }, (error) => Promise.reject(error) ); service.interceptors.response.use( async (response) => { // 接口响应之后把这次请求清除 abortController.removePending(response.config); const res: any = response.data; let { code } = res; if (response.request.responseType === 'blob') { const _res = await blob2json(res); if (_res && _res.code) { code = _res.code; } } if (code === '2002') { ElMessage.error('登录过期,请重新登录'); reload(); } else if (code !== '1001' && !Array.isArray(res)) { const msg = res.msg || '接口请求出错'; ElMessage.error(msg); return Promise.reject(msg); } else { ElMessage({ message: '请求成功', type: 'success', }); return resolveData(res.data || res); } }, (error) => { const msg = error && error.message ? error.message : error; ElMessage.error(msg); if (axios.isCancel(error)) { console.error('请求被取消', error); } return Promise.reject('错误信息' + msg); } ); export default service; |
上一篇:Nuxt3踩坑经验贴-荆棘载途的坑我来踩,免费的知识大家一起学
下一篇:JavaScript-ES6中你不能不知道的map对象的基本使用
支付宝扫一扫打赏
微信扫一扫打赏
共 0 条评论关于"Axios.js通过AbortController中止控制器方法取消请求"
最新评论