Vue?Axios請(qǐng)求取消和重發(fā)封裝的實(shí)現(xiàn)代碼
場(chǎng)景

如圖,有時(shí)候請(qǐng)求后端同一接口時(shí),會(huì)因接口響應(yīng)速度過慢,導(dǎo)致前端原本頁面應(yīng)顯示a,結(jié)果顯示b。像上圖,前端頁面看起來就是先顯示 請(qǐng)求b 的數(shù)據(jù),然后是 請(qǐng)求c 的數(shù)據(jù),最后是 請(qǐng)求a 的數(shù)據(jù),但是實(shí)際上我們只需要顯示最后的 請(qǐng)求c 的數(shù)據(jù)。而且這樣還會(huì)導(dǎo)致頁面渲染出現(xiàn)閃爍。
需求
- 實(shí)現(xiàn)取消重復(fù)請(qǐng)求(有多種取消方式)
- 發(fā)出第一個(gè)請(qǐng)求,取消后面的相同請(qǐng)求
- 取消前面的請(qǐng)求,只發(fā)出最后一個(gè)請(qǐng)求
- 實(shí)現(xiàn)請(qǐng)求失敗后,自動(dòng)重發(fā)指定次數(shù)
實(shí)現(xiàn)1(發(fā)出第一個(gè)請(qǐng)求,取消后面的相同請(qǐng)求)
import Axios from "axios";
// 創(chuàng)建一個(gè)實(shí)例
const service = Axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 1000,
});
// 添加請(qǐng)求攔截器
service.interceptors.request.use(
(config) => {
// 判斷是否有 token
if (getToken()) {
config.headers["Permission-Token"] = getToken();
}
// 添加待處理請(qǐng)求
addPendingRequest(config);
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 添加響應(yīng)攔截器
service.interceptors.response.use(
(response) => {
// 移除掛起的請(qǐng)求
removePendingRequest(response);
const res = response.data;
return res;
},
(error) => {
// 移除掛起的請(qǐng)求
removePendingRequest(error);
// 是否是取消請(qǐng)求
if (Axios.isCancel(error)) {
console.log("err:", error);
return Promise.reject(error);
} else {
console.log("請(qǐng)求有問題:", error.message);
// 當(dāng)請(qǐng)求超時(shí)、斷網(wǎng)等問題時(shí)重發(fā)
return againRequest(error);
}
}
);
export default service;
const pendingRequest = new Map();
/**
* 添加待處理請(qǐng)求
* @description 該函數(shù)用于管理重復(fù)請(qǐng)求,避免同一接口的多個(gè)請(qǐng)求同時(shí)進(jìn)行
* @param {Object} config - axios請(qǐng)求配置對(duì)象
*/
function addPendingRequest(config) {
// 創(chuàng)建axios的取消令牌源,用于請(qǐng)求的取消控制
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
// 生成請(qǐng)求的唯一鍵,用于標(biāo)識(shí)不同的請(qǐng)求
const requestKey = generateReqKey(config);
// 方法一
// 如果相同請(qǐng)求已在進(jìn)行中,則取消當(dāng)前請(qǐng)求
if (pendingRequest.has(requestKey)) {
config.cancelToken = source.token;
source.cancel(`${config.url} 請(qǐng)求已取消`);
} else {
// 如果沒有相同的請(qǐng)求在進(jìn)行中,則設(shè)置取消令牌并存儲(chǔ)請(qǐng)求鍵和取消函數(shù)的映射
config.cancelToken = source.token;
pendingRequest.set(requestKey, source.token);
}
// 方法二
// 如果相同請(qǐng)求已在進(jìn)行中,則取消當(dāng)前請(qǐng)求
if (pendingRequest.has(requestKey)) {
config.cancelToken = new Axios.CancelToken((cancel) => {
// cancel 函數(shù)的參數(shù)會(huì)作為 promise 的 error 被捕獲
cancel(`${config.url} 請(qǐng)求已取消`);
});
} else {
config.cancelToken =
new Axios.CancelToken((cancel) => {
pendingRequest.set(requestKey, cancel);
});
}
// 以上兩種方式都能取消請(qǐng)求,但是從 Axios `v0.22.0` 開始已被棄用,從 `v0.22.0` 開始,Axios 支持以 fetch API 方式—— `AbortController`取消請(qǐng)求
}
/**
* 移除掛起的請(qǐng)求
* @description 當(dāng)收到響應(yīng)時(shí),檢查并移除與該響應(yīng)相關(guān)的掛起請(qǐng)求
* @param {Object} response - 響應(yīng)對(duì)象,包含請(qǐng)求配置信息
*/
function removePendingRequest(response) {
// 如果 response.config 返回的是 undefined,表示是取消請(qǐng)求,則不處理
if (response.config) {
// 生成請(qǐng)求的唯一鍵,用于標(biāo)識(shí)不同的請(qǐng)求
const requestKey = generateReqKey(response.config);
// 判斷是否有這個(gè) key,有就刪除
if (pendingRequest.has(requestKey)) {
pendingRequest.delete(requestKey);
}
}
}
/**
* 處理請(qǐng)求重試的函數(shù)
* @description 它嘗試重新發(fā)送因錯(cuò)誤而失敗的請(qǐng)求
* @param {Object} err - 包含錯(cuò)誤信息和配置的對(duì)象
* @returns {Promise} 重新發(fā)送請(qǐng)求的Promise對(duì)象,解決或拒絕依據(jù)重試邏輯
*/
function againRequest(err) {
const config = err.config;
// 如果配置對(duì)象不存在,則直接拒絕Promise
if (!config) return Promise.reject(err);
// 設(shè)置用于記錄重試計(jì)數(shù)的變量 默認(rèn)為0
config._retryCount = config._retryCount || 0;
// 如果重試次數(shù)已經(jīng)達(dá)到3次或更多,則拒絕Promise
if (config._retryCount >= 3) {
return Promise.reject(err);
}
// 重試次數(shù)
config._retryCount += 1;
// 延時(shí)處理,用于控制重試的時(shí)間間隔
var backoff = new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
});
// 在延時(shí)結(jié)束后,重新發(fā)起請(qǐng)求
return backoff.then(() => {
// 重新發(fā)起axios請(qǐng)求,使用更新后的配置
return service(config);
});
}
/**
* 生成請(qǐng)求的唯一鍵
* @description 這個(gè)函數(shù)的目的是根據(jù)請(qǐng)求配置生成一個(gè)唯一的鍵,用于緩存請(qǐng)求數(shù)據(jù)
* @param {Object} config - 請(qǐng)求配置對(duì)象,包含了url、method、params和data屬性
* @returns {string} - 返回一個(gè)根據(jù)請(qǐng)求配置生成的唯一鍵
*/
function generateReqKey(config) {
// 請(qǐng)求得到的config.data是對(duì)象格式,響應(yīng)回來的response.config.data是字符串格式,需要轉(zhuǎn)換成對(duì)象
if (typeof config.data === "string") {
config.data = JSON.parse(config.data);
}
const { url, method, params, data } = config;
return [url, method, JSON.stringify(params), JSON.stringify(data)].join("&");
}
實(shí)現(xiàn)2(取消前面的請(qǐng)求,只發(fā)出最后一個(gè)請(qǐng)求)
import Axios from "axios";
// 創(chuàng)建一個(gè)實(shí)例
const service = Axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 1000,
});
// 添加請(qǐng)求攔截器
service.interceptors.request.use(
(config) => {
// 判斷是否有 token
if (getToken()) {
config.headers["Permission-Token"] = getToken();
}
// 添加待處理請(qǐng)求
addPendingRequest(config);
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 添加響應(yīng)攔截器
service.interceptors.response.use(
(response) => {
const res = response.data;
return res;
},
(error) => {
// 是否是取消請(qǐng)求
if (!axios.isCancel(error)) {
// 當(dāng)請(qǐng)求超時(shí)、斷網(wǎng)等問題時(shí)重發(fā)
againRequest(error);
}
}
);
export default service;
let pendingRequsetList = [];
/**
* 取消所有待處理的請(qǐng)求
*/
export const cancelPendingRequest = () => {
// 遍歷待處理的請(qǐng)求列表,調(diào)用每個(gè)請(qǐng)求的取消方法
pendingRequsetList.forEach((item) => {
item.cancel();
});
// 清空待處理的請(qǐng)求列表
pendingRequsetList = [];
};
/**
* 添加待處理的請(qǐng)求
* @description 為配置對(duì)象添加取消令牌,以便在請(qǐng)求待處理時(shí)能夠取消
* @param {Object} config - 請(qǐng)求的配置對(duì)象
*/
function addPendingRequest(config) {
// 創(chuàng)建一個(gè)新的取消令牌,并將取消函數(shù)存儲(chǔ)到待處理請(qǐng)求列表中
config.cancelToken = new Axios.CancelToken((cancel) => {
pendingRequsetList.push({
cancel,
});
});
}
/**
* 處理請(qǐng)求失敗后的重試機(jī)制
* @description 當(dāng)請(qǐng)求失敗時(shí),嘗試再次請(qǐng)求,最多重試3次
* @param {Object} err - 錯(cuò)誤對(duì)象,包含請(qǐng)求的配置信息
* @returns {Promise} 重試請(qǐng)求的Promise對(duì)象
*/
function againRequest(err) {
// 獲取請(qǐng)求的配置信息
const config = err.config;
// 如果沒有配置信息,則直接拒絕Promise
if (!config) Promise.reject(err);
// 初始化重試次數(shù),如果不存在則為0
config._retryCount = config._retryCount || 0;
// 如果重試次數(shù)已經(jīng)達(dá)到3次,則拒絕Promise
if (config._retryCount >= 3) {
return Promise.reject(err);
}
// 每次重試前,重試次數(shù)加1
config._retryCount += 1;
// 創(chuàng)建一個(gè)Promise,用于實(shí)現(xiàn)指數(shù)退避機(jī)制
const backoff = new Promise((resolve) => {
// 設(shè)置一個(gè)定時(shí)器,1秒后 resolve Promise
setTimeout(() => {
resolve();
}, 1000);
});
// 返回一個(gè)Promise,表示再次請(qǐng)求的操作
return backoff.then(() => {
return service(config);
});
}
在組件中使用,發(fā)送一個(gè)新的請(qǐng)求之前,取消前面的所有正在請(qǐng)求的請(qǐng)求,如下:
<script>
import { cancelPendingRequest } from '@/utils/request' // 導(dǎo)入取消請(qǐng)求的方法
export default {
methods: {
getList() {
cancelPendingRequest() // 取消所有正在發(fā)送請(qǐng)求的請(qǐng)求
axios.get() // 發(fā)送新的請(qǐng)求
}
}
}
</script>
如果不想每次使用取消請(qǐng)求的時(shí)候都需要導(dǎo)入那么可以掛載到Vue的原型上面,可以在main.js文件中
import Vue from 'vue'
import { cancelPendingRequest } from '@/utils/request'
Vue.prototype.$cancelPendingRequest = cancelPendingRequest // 掛載到 Vue 原型上
另外如果想每次路由頁面跳轉(zhuǎn)時(shí),取消上一個(gè)頁面的所有請(qǐng)求,我們可以在全局路由守衛(wèi)那里進(jìn)行處理,具體如下:
import router from "./router";
import Vue from 'vue'
router.beforeEach((to, from, next) => {
Vue.$cancelPendingRequest() // 跳轉(zhuǎn)頁面之前取消之前的所有請(qǐng)求
})
以上就是Vue Axios請(qǐng)求取消和重發(fā)的封裝的實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于Vue Axios請(qǐng)求取消和重發(fā)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
VUE使用DXFParser組件解析dxf文件生成圖片的操作代碼
這篇文章主要介紹了VUE使用DXFParser組件解析dxf文件生成圖片的操作代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
Vuex子模塊調(diào)用子模塊的actions或mutations實(shí)現(xiàn)方式
這篇文章主要介紹了Vuex子模塊調(diào)用子模塊的actions或mutations實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
Element-UI組件實(shí)現(xiàn)面包屑導(dǎo)航欄的示例代碼
面包屑導(dǎo)航欄是一種用戶界面組件,用于展示用戶在網(wǎng)站或應(yīng)用中的路徑,它包括了從主頁到當(dāng)前頁面的鏈接序列,有助于用戶快速了解和導(dǎo)航至上級(jí)頁面,本文就來介紹一下Element-UI組件實(shí)現(xiàn)面包屑導(dǎo)航欄的示例代碼,感興趣的可以了解一下2024-09-09
vue中el-autocomplete與el-select的異同
本文主要介紹了vue中el-autocomplete與el-select的異同,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
ElementUI組件Dialog彈窗再次打開表單仍顯示上次數(shù)據(jù)的問題
這篇文章主要介紹了ElementUI組件Dialog彈窗再次打開表單仍顯示上次數(shù)據(jù)的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
springboot?vue接口測(cè)試前端動(dòng)態(tài)增刪表單功能實(shí)現(xiàn)
這篇文章主要為大家介紹了springboot?vue接口測(cè)試前端動(dòng)態(tài)增刪表單功能實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
vue3中setup-script的應(yīng)用實(shí)例
script-setup是一個(gè)比較有爭(zhēng)議的新特性,作為 setup 函數(shù)的語法糖,褒貶不一,不過經(jīng)歷了幾次迭代之后,目前在體驗(yàn)上來說,感受還是非常棒的,這篇文章主要給大家介紹了關(guān)于vue3中setup-script應(yīng)用的相關(guān)資料,需要的朋友可以參考下2022-01-01
Vue3+X6流程圖實(shí)現(xiàn)數(shù)據(jù)雙向綁定詳解
這篇文章主要為大家詳細(xì)介紹了Vue3如何結(jié)合X6流程圖實(shí)現(xiàn)數(shù)據(jù)雙向綁定,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03

