在Vue3項(xiàng)目中取消重復(fù)請(qǐng)求的兩種方案
今天我們來(lái)聊聊一個(gè)實(shí)際開(kāi)發(fā)中常見(jiàn)的問(wèn)題:在Vue3項(xiàng)目中,如何取消重復(fù)的請(qǐng)求。
為什么需要取消重復(fù)請(qǐng)求?
這樣做有幾個(gè)明顯的好處:
- 節(jié)省資源:釋放瀏覽器連接,減輕服務(wù)器壓力。
- 保證數(shù)據(jù)正確性:避免因請(qǐng)求響應(yīng)順序錯(cuò)亂導(dǎo)致頁(yè)面顯示舊數(shù)據(jù)。
- 提升用戶體驗(yàn):防止無(wú)效請(qǐng)求阻塞后續(xù)有效操作。
在Vue3的生態(tài)中,我們最常用的HTTP客戶端是 axios。因此,本文的核心將圍繞如何使用 axios 的取消令牌(CancelToken)及其更現(xiàn)代的替代品——AbortController——來(lái)實(shí)現(xiàn)請(qǐng)求取消。
方案一:使用 Axios 的 CancelToken
axios 從很早就支持通過(guò) CancelToken 來(lái)取消請(qǐng)求。雖然它在 axios v0.22.0 之后被標(biāo)記為已棄用,轉(zhuǎn)而推薦使用標(biāo)準(zhǔn)的 AbortController,但很多現(xiàn)有項(xiàng)目仍在使用這個(gè)API,所以我們有必要了解一下。 它的工作原理是:創(chuàng)建一個(gè)“取消令牌”的源(source),將這個(gè)令牌配置到請(qǐng)求中。當(dāng)我們需要取消請(qǐng)求時(shí),調(diào)用這個(gè)源的 cancel 方法。
基本使用示例
import?axios?from?'axios';
// 1. 創(chuàng)建一個(gè) CancelToken Source
const?source = axios.CancelToken.source();
// 2. 發(fā)起請(qǐng)求,并配置 cancelToken
axios.get('/api/user/123', {
? cancelToken: source.token
}).then(response?=>?{
? console.log(response.data);
}).catch(function?(thrown) {
? // 判斷錯(cuò)誤是否是因?yàn)檎?qǐng)求被取消
? if?(axios.isCancel(thrown)) {
? ? console.log('請(qǐng)求被取消:', thrown.message);
? }?else?{
? ? // 處理其他錯(cuò)誤
? }
});
// 3. 在需要的時(shí)候取消請(qǐng)求
source.cancel('操作被用戶取消');
在Vue3組件中管理重復(fù)請(qǐng)求
在實(shí)際項(xiàng)目中,我們通常需要一種機(jī)制來(lái)管理“同一個(gè)”重復(fù)請(qǐng)求。一個(gè)常見(jiàn)的思路是:將每個(gè)請(qǐng)求的唯一標(biāo)識(shí)(例如:請(qǐng)求方法+URL)和一個(gè)取消函數(shù)關(guān)聯(lián)起來(lái),在發(fā)起新請(qǐng)求前,檢查并取消舊的、未完成的相同請(qǐng)求。
我們可以利用Vue3的響應(yīng)式系統(tǒng)和組合式API,封裝一個(gè)可復(fù)用的邏輯。
首先,我們創(chuàng)建一個(gè)用于存儲(chǔ)所有進(jìn)行中請(qǐng)求的Map,以及相關(guān)的操作函數(shù)。
// utils/request.js
import?axios?from?'axios';
// 存儲(chǔ)所有進(jìn)行中請(qǐng)求的Map
const?pendingRequestMap =?new?Map();
/**
?* 生成請(qǐng)求的唯一標(biāo)識(shí)鍵
?*?@param?{*} config axios的請(qǐng)求配置對(duì)象
?*?@returns?{string} 唯一鍵
?*/
function?generateReqKey(config) {
? const?{ method, url, params, data } = config;
? // 簡(jiǎn)單拼接,可根據(jù)業(yè)務(wù)復(fù)雜化(如對(duì)data排序)
? return?[method, url,?JSON.stringify(params),?JSON.stringify(data)].join('&');
}
/**
?* 添加請(qǐng)求到等待隊(duì)列
?*?@param?{*} config axios的請(qǐng)求配置對(duì)象
?*/
function?addPendingRequest(config) {
? const?requestKey =?generateReqKey(config);
? // 為這個(gè)請(qǐng)求創(chuàng)建一個(gè)取消令牌源
? config.cancelToken?= config.cancelToken?||?new?axios.CancelToken((cancel) =>?{
? ? // 如果Map中還沒(méi)有這個(gè)key,則添加進(jìn)去
? ? if?(!pendingRequestMap.has(requestKey)) {
? ? ? pendingRequestMap.set(requestKey, cancel);
? ? }
? });
}
/**
?* 移除等待隊(duì)列中的請(qǐng)求
?*?@param?{*} config axios的請(qǐng)求配置對(duì)象
?*/
function?removePendingRequest(config) {
? const?requestKey =?generateReqKey(config);
? if?(pendingRequestMap.has(requestKey)) {
? ? // 如果在Map中存在這個(gè)請(qǐng)求,說(shuō)明它還未完成,將其取消并從Map中移除
? ? const?cancel = pendingRequestMap.get(requestKey);
? ? cancel(requestKey);?// 取消請(qǐng)求,可以傳遞消息
? ? pendingRequestMap.delete(requestKey);
? }
}
// 創(chuàng)建axios實(shí)例
const?service = axios.create({
? timeout:?10000,
});
// 請(qǐng)求攔截器:在請(qǐng)求發(fā)出前,檢查并取消重復(fù)請(qǐng)求
service.interceptors.request.use(
? (config) =>?{
? ? // 檢查并取消之前的相同請(qǐng)求
? ? removePendingRequest(config);
? ? // 將當(dāng)前請(qǐng)求添加到等待隊(duì)列
? ? addPendingRequest(config);
? ? return?config;
? },
? (error) =>?{
? ? return?Promise.reject(error);
? }
);
// 響應(yīng)攔截器:請(qǐng)求完成后(無(wú)論成功失?。瑢⑵鋸牡却?duì)列中移除
service.interceptors.response.use(
? (response) =>?{
? ? removePendingRequest(response.config);
? ? return?response;
? },
? (error) =>?{
? ? // 如果錯(cuò)誤是因?yàn)槿∠?qǐng)求造成的,我們選擇忽略這個(gè)錯(cuò)誤,不拋出到業(yè)務(wù)層
? ? if?(axios.isCancel(error)) {
? ? ? console.log('已取消的重復(fù)請(qǐng)求:', error.message);
? ? ? return?new?Promise(() =>?{});?// 返回一個(gè)“永遠(yuǎn)pending”的Promise,中斷Promise鏈
? ? }
? ? // 對(duì)于其他錯(cuò)誤,移除請(qǐng)求并正常拋出
? ? removePendingRequest(error.config?|| {});
? ? return?Promise.reject(error);
? }
);
export?default?service;
然后,在Vue組件中,你可以直接使用這個(gè)封裝好的 service 來(lái)發(fā)起請(qǐng)求。它會(huì)自動(dòng)處理重復(fù)請(qǐng)求的取消。
<script setup>
import?{ ref }?from?'vue';
import?request?from?'@/utils/request';
const?searchResults =?ref([]);
const?loading =?ref(false);
const?handleSearch?=?async?(keyword) => {
? loading.value?=?true;
? try?{
? ? const?response =?await?request.get('/api/search', {
? ? ? params: { keyword }
? ? });
? ? searchResults.value?= response.data;
? }?catch?(error) {
? ? // 這里不會(huì)捕獲到因重復(fù)請(qǐng)求被取消而拋出的錯(cuò)誤,因?yàn)槲覀冊(cè)跀r截器中已經(jīng)處理了
? ? console.error('搜索失敗:', error);
? }?finally?{
? ? loading.value?=?false;
? }
};
</script>
注意:CancelToken 方式在 axios 新版本中已被標(biāo)記為棄用。對(duì)于新項(xiàng)目,建議直接使用下面介紹的更現(xiàn)代的 AbortController 方案。
方案二: AbortController
AbortController 是一個(gè)現(xiàn)代的Web API,它提供了一種更通用、更標(biāo)準(zhǔn)的方式來(lái)中止一個(gè)或多個(gè)Web請(qǐng)求。fetch API 和新的 axios 版本都原生支持它。使用 AbortController 是當(dāng)前推薦的做法。
基本概念
AbortController:控制器對(duì)象,用于觸發(fā)中止信號(hào)。AbortSignal:信號(hào)對(duì)象,關(guān)聯(lián)到具體的請(qǐng)求上??刂破骺梢酝ㄟ^(guò)它來(lái)通知請(qǐng)求“需要中止”。
基本使用示例
// 1. 創(chuàng)建一個(gè) AbortController 實(shí)例
const?controller =?new?AbortController();
const?signal = controller.signal;?// 獲取它的 signal
// 2. 發(fā)起 fetch 請(qǐng)求,并將 signal 關(guān)聯(lián)上去
fetch('/api/some-data', { signal })
? .then(response?=>?response.json())
? .then(data?=>?console.log(data))
? .catch(err?=>?{
? ? // 如果錯(cuò)誤是因?yàn)檎?qǐng)求被中止
? ? if?(err.name?===?'AbortError') {
? ? ? console.log('Fetch 請(qǐng)求被中止');
? ? }?else?{
? ? ? console.error('其他錯(cuò)誤:', err);
? ? }
? });
// 3. 在需要的時(shí)候中止請(qǐng)求
controller.abort();?// 這會(huì)觸發(fā) signal 的中止事件,從而取消請(qǐng)求
在 Axios 中使用 AbortController
從 axios v0.22.0 開(kāi)始,你可以使用 signal 屬性來(lái)配置 AbortSignal。
import?axios?from?'axios';
const?controller =?new?AbortController();
axios.get('/api/user/123', {
? signal: controller.signal?// 將 signal 傳遞給請(qǐng)求配置
}).then(response?=>?{
? console.log(response.data);
}).catch(function?(error) {
? // 判斷錯(cuò)誤是否是因?yàn)檎?qǐng)求被取消
? if?(axios.isCancel(error)) {
? ? console.log('請(qǐng)求被取消:', error.message);
? }?else?{
? ? // 處理其他錯(cuò)誤
? }
});
// 取消請(qǐng)求
controller.abort();
封裝基于 AbortController 的重復(fù)請(qǐng)求取消
我們可以用類似的思路,用 AbortController 重構(gòu)之前的工具函數(shù)。主要變化是將存儲(chǔ)的 cancel 函數(shù)替換為 AbortController 實(shí)例。
// utils/request-abort.js
import?axios?from?'axios';
const?pendingRequestMap =?new?Map();
function?generateReqKey(config) {
? const?{ method, url, params, data } = config;
? return?[method, url,?JSON.stringify(params),?JSON.stringify(data)].join('&');
}
function?addPendingRequest(config) {
? const?requestKey =?generateReqKey(config);
? // 如果已有相同請(qǐng)求在進(jìn)行,則中止它
? if?(pendingRequestMap.has(requestKey)) {
? ? const?oldController = pendingRequestMap.get(requestKey);
? ? oldController.abort();?// 中止舊的請(qǐng)求
? ? pendingRequestMap.delete(requestKey);
? }
? // 為當(dāng)前請(qǐng)求創(chuàng)建新的控制器并存儲(chǔ)
? const?controller =?new?AbortController();
? config.signal?= controller.signal;?// 關(guān)鍵:將 signal 賦給請(qǐng)求配置
? pendingRequestMap.set(requestKey, controller);
}
function?removePendingRequest(config) {
? const?requestKey =?generateReqKey(config);
? if?(pendingRequestMap.has(requestKey)) {
? ? // 請(qǐng)求完成,直接從Map中移除即可,不需要手動(dòng)abort
? ? pendingRequestMap.delete(requestKey);
? }
}
const?service = axios.create({
? timeout:?10000,
});
service.interceptors.request.use(
? (config) =>?{
? ? removePendingRequest(config);?// 先移除可能存在的舊記錄(清理作用)
? ? addPendingRequest(config);?// 添加新請(qǐng)求
? ? return?config;
? },
? (error) =>?{
? ? return?Promise.reject(error);
? }
);
service.interceptors.response.use(
? (response) =>?{
? ? removePendingRequest(response.config);
? ? return?response;
? },
? (error) =>?{
? ? // 判斷錯(cuò)誤是否由取消請(qǐng)求導(dǎo)致
? ? if?(axios.isCancel(error)) {
? ? ? console.log('已取消的重復(fù)請(qǐng)求:', error.message);
? ? ? return?new?Promise(() =>?{});?// 中斷Promise鏈
? ? }
? ? removePendingRequest(error.config?|| {});
? ? return?Promise.reject(error);
? }
);
export?default?service;
這個(gè)版本的邏輯更清晰:在添加新請(qǐng)求時(shí),直接中止舊的相同請(qǐng)求。響應(yīng)攔截器里只需要做清理工作。
進(jìn)階與優(yōu)化
上面的方案解決了核心問(wèn)題,但在實(shí)際項(xiàng)目中,我們可能還需要考慮更多細(xì)節(jié)。
1. 白名單控制
有些請(qǐng)求我們可能不希望被自動(dòng)取消。例如,一個(gè)輪詢定時(shí)請(qǐng)求,或者多個(gè)并行的不同數(shù)據(jù)請(qǐng)求。我們可以通過(guò)給請(qǐng)求配置添加一個(gè)自定義標(biāo)記(如 allowRepeat: true)來(lái)實(shí)現(xiàn)白名單。
// 在攔截器中
service.interceptors.request.use(
? (config) =>?{
? ? // 如果配置了允許重復(fù),則跳過(guò)取消邏輯
? ? if?(config.allowRepeat) {
? ? ? return?config;
? ? }
? ? removePendingRequest(config);
? ? addPendingRequest(config);
? ? return?config;
? }
);
// 在組件中使用
request.get('/api/polling', {?allowRepeat:?true?});
2. 與 Vue Router 導(dǎo)航守衛(wèi)結(jié)合
當(dāng)用戶切換頁(yè)面時(shí),我們通常希望取消所有未完成的請(qǐng)求,以免它們?cè)诤笈_(tái)繼續(xù)運(yùn)行并可能更新一個(gè)已經(jīng)銷(xiāo)毀的組件狀態(tài)。這可以在Vue Router的全局前置守衛(wèi)中實(shí)現(xiàn)。
// router/index.js
import?router?from?'./router';
import?{ pendingRequestMap }?from?'@/utils/request-abort';?// 需要將map導(dǎo)出
router.beforeEach((to,?from, next) =>?{
? // 遍歷并中止所有進(jìn)行中的請(qǐng)求
? pendingRequestMap.forEach((controller, key) =>?{
? ? controller.abort(`路由跳轉(zhuǎn)至?${to.path}`);
? });
? // 清空Map
? pendingRequestMap.clear();
? next();
});
3. 使用 Composition API 封裝
在Vue3中,我們可以利用組合式API,將取消邏輯封裝成一個(gè)更優(yōu)雅、可復(fù)用的 useRequest Hook。
// composables/useRequest.js
import?{ ref, onUnmounted }?from?'vue';
import?request?from?'@/utils/request-abort';
export?function?useRequest() {
? const?data =?ref(null);
? const?error =?ref(null);
? const?loading =?ref(false);
? let?abortController =?null;
? const?execute?=?async?(config) => {
? ? loading.value?=?true;
? ? error.value?=?null;
? ? // 為這次執(zhí)行創(chuàng)建一個(gè)獨(dú)立的控制器(可選,如果全局已管理則可省略)
? ? abortController =?new?AbortController();
? ? try?{
? ? ? const?response =?await?request({
? ? ? ? ...config,
? ? ? ? signal: abortController.signal
? ? ? });
? ? ? data.value?= response.data;
? ? ? return?response;
? ? }?catch?(err) {
? ? ? if?(!axios.isCancel(err)) {
? ? ? ? error.value?= err;
? ? ? }
? ? ? throw?err;
? ? }?finally?{
? ? ? loading.value?=?false;
? ? }
? };
? // 組件卸載時(shí),取消由這個(gè)Hook發(fā)起的請(qǐng)求
? onUnmounted(() =>?{
? ? if?(abortController) {
? ? ? abortController.abort();
? ? }
? });
? // 提供一個(gè)手動(dòng)取消的方法
? const?cancel?= () => {
? ? if?(abortController) {
? ? ? abortController.abort();
? ? }
? };
? return?{
? ? data,
? ? error,
? ? loading,
? ? execute,
? ? cancel
? };
}
在組件中使用:
<script setup>
import?{ useRequest }?from?'@/composables/useRequest';
const?{ data, loading, execute } =?useRequest();
const?handleSubmit?= () => {
? execute({
? ? method:?'post',
? ? url:?'/api/submit',
? ? data: {?/* ... */?}
? });
};
</script>
取消重復(fù)請(qǐng)求是一個(gè)看似簡(jiǎn)單,但能顯著提升應(yīng)用健壯性的優(yōu)化點(diǎn)。在Vue3項(xiàng)目中,結(jié)合 axios 和 AbortController,我們可以用清晰的代碼實(shí)現(xiàn)這一功能。希望本文介紹的方法和思路,能幫助你更好地處理項(xiàng)目中的網(wǎng)絡(luò)請(qǐng)求問(wèn)題。
以上就是在Vue3項(xiàng)目中取消重復(fù)請(qǐng)求的兩種方案的詳細(xì)內(nèi)容,更多關(guān)于Vue3取消重復(fù)請(qǐng)求方案的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
前端低代碼form-generator實(shí)現(xiàn)及新增自定義組件詳解
這篇文章主要給大家介紹了關(guān)于前端低代碼form-generator實(shí)現(xiàn)及新增自定義組件的相關(guān)資料,form-generator是一個(gè)開(kāi)源的表單生成器,可以幫助我們快速構(gòu)建各種表單頁(yè)面,需要的朋友可以參考下2023-11-11
vue實(shí)現(xiàn)簡(jiǎn)單圖片上傳功能
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)簡(jiǎn)單圖片上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Vue2實(shí)現(xiàn)未登錄攔截頁(yè)面功能的基本步驟和示例代碼
在Vue 2中實(shí)現(xiàn)未登錄攔截頁(yè)面功能,通常可以通過(guò)路由守衛(wèi)和全局前置守衛(wèi)來(lái)完成,以下是一個(gè)基本的實(shí)現(xiàn)步驟和示例代碼,幫助你創(chuàng)建一個(gè)簡(jiǎn)單的未登錄攔截邏輯,需要的朋友可以參考下2024-04-04
Vue使用openlayers實(shí)現(xiàn)繪制圓形和多邊形
這篇文章主要為大家詳細(xì)介紹了Vue如何使用openlayers實(shí)現(xiàn)繪制圓形和多邊形,文中的示例代碼講解詳細(xì),感興趣的小伙伴快跟隨小編一起動(dòng)手嘗試一下2022-06-06
SpringBoot+Vue前后端分離,使用SpringSecurity完美處理權(quán)限問(wèn)題的解決方法
這篇文章主要介紹了SpringBoot+Vue前后端分離,使用SpringSecurity完美處理權(quán)限問(wèn)題,需要的朋友可以參考下2018-01-01
Vue實(shí)現(xiàn)鼠標(biāo)經(jīng)過(guò)文字顯示懸浮框效果的示例代碼
這篇文章主要介紹了Vue實(shí)現(xiàn)鼠標(biāo)經(jīng)過(guò)文字顯示懸浮框效果,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
基于vue-cli 路由 實(shí)現(xiàn)類似tab切換效果(vue 2.0)
這篇文章主要介紹了基于vue-cli 路由 實(shí)現(xiàn)類似tab切換效果(vue 2.0),非常不錯(cuò),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-05-05

