Vue3+Axios:请求出错时取消所有待处理请求的实现方案
解决方案:Axios并发请求异常时取消所有待处理请求 + 修复加载指示器问题
一、核心问题分析
- 加载指示器异常:原代码用单个布尔值
isLoading控制状态,并发请求时第一个请求完成就将其设为false,导致后续请求仍在进行但加载状态已关闭。 - 异常时未取消请求:单个请求报错后,剩余待处理请求仍会继续执行,不符合需求。
二、方案1:基于AbortController批量取消并发请求
通过维护待处理请求的映射表,结合AbortController实现请求的批量取消,同时改用计数模式修复加载状态问题。
1. 修改Pinia配置Store
先在配置Store中添加请求计数状态,用getter计算加载状态:
// @/stores/configuration-store.ts import { defineStore } from 'pinia'; import { EnvironmentEnum } from '@/interfaces/environment-enum'; export const useConfigStore = defineStore('config', { state: () => ({ selectedEnvironment: EnvironmentEnum.Production, // 替换你的默认环境 loadingCount: 0, // 请求计数,替代原布尔值isLoading }), getters: { isLoading: (state) => state.loadingCount > 0, // 通过计数自动判断加载状态 }, });
2. 改造Axios实例(axiosClient.ts)
import { notify } from '@/services/notificationService'; import axios, { AxiosError, AxiosRequestConfig } from 'axios'; import { storeToRefs } from 'pinia'; import { EnvironmentEnum } from './interfaces/environment-enum'; import { useConfigStore } from '@/stores/configuration-store'; import axiosRetry from 'axios-retry'; const axiosClient = axios.create({ withCredentials: true, }); // 注意:将重试逻辑绑定到当前axios实例,而非全局axios axiosRetry(axiosClient, { retries: 3, retryDelay: () => 1000, }); // 维护待处理请求:key为请求唯一标识,value为对应AbortController const pendingRequests = new Map<string, AbortController>(); // 生成请求唯一标识(method+url确保唯一性) const generateRequestKey = (config: AxiosRequestConfig): string => { return `${config.method?.toUpperCase() || 'GET'}:${config.url}`; }; // 请求拦截器 axiosClient.interceptors.request.use((config) => { const store = useConfigStore(); const { loadingCount } = storeToRefs(store); const { selectedEnvironment } = storeToRefs(store); config.baseURL = `${EnvironmentEnum[selectedEnvironment.value]}/`; // 创建AbortController并绑定到请求 const controller = new AbortController(); config.signal = controller.signal; // 记录待处理请求 const requestKey = generateRequestKey(config); pendingRequests.set(requestKey, controller); // 增加请求计数 loadingCount.value += 1; return config; }); // 响应拦截器 axiosClient.interceptors.response.use( (response) => { const store = useConfigStore(); const { loadingCount } = storeToRefs(store); const requestKey = generateRequestKey(response.config); // 移除已完成的请求记录 pendingRequests.delete(requestKey); // 减少请求计数(确保不出现负数) loadingCount.value = Math.max(0, loadingCount.value - 1); return response; }, (error: AxiosError) => { const store = useConfigStore(); const { loadingCount } = storeToRefs(store); // 取消所有待处理请求 pendingRequests.forEach(controller => controller.abort()); pendingRequests.clear(); // 重置请求计数 loadingCount.value = 0; // 错误提示 const errorMsg = error.code === 'ERR_NETWORK' ? error.message : JSON.stringify(error.response?.data || error.message); notify(errorMsg, 'danger'); // 必须返回reject,否则调用处无法捕获错误 return Promise.reject(error); } ); export default axiosClient;
3. 组件中使用(无需修改原有请求逻辑)
只需确保组件内的search函数添加async关键字:
const search = async () => { const data = ref(); try { let result = await call1(); if(result && result.status === 200) { data.value = await call2(); } } catch (err) { // 捕获请求取消或其他错误 console.error('请求异常:', err); } };
三、方案2:请求队列化(串行执行)
如果业务允许请求串行执行,可通过队列方式让请求依次执行,前一个请求失败则终止后续请求,同时天然避免加载状态异常。
1. 在Axios实例中添加队列方法
// 在axiosClient.ts末尾添加 let requestQueue: Promise<any> = Promise.resolve(); // 封装队列请求方法 axiosClient.queueRequest = async <T>(config: AxiosRequestConfig): Promise<T> => { requestQueue = requestQueue.then(async () => { try { return await axiosClient(config); } catch (err) { // 出错后清空队列,后续请求不再执行 requestQueue = Promise.resolve(); throw err; } }); return requestQueue; };
2. 组件中使用队列请求
const search = async () => { const data = ref(); try { // 用queueRequest替代原请求方法 let result = await axiosClient.queueRequest({ url: '/api/call1', method: 'GET' }); if(result && result.status === 200) { data.value = await axiosClient.queueRequest({ url: '/api/call2', method: 'GET' }); } } catch (err) { console.error('请求中断:', err); } };
方案选择建议
- 方案1:适合需要并发请求、但要求异常时快速终止所有待处理请求的场景;
- 方案2:适合业务逻辑本身要求请求串行执行的场景,实现更简单,无需处理并发状态。
内容的提问来源于stack exchange,提问作者BondAddict
相关产品推荐
相关产品推荐

