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

