Axios取消重複請求的方法例項詳解
前言
在 Web 專案開發過程中,我們經常會遇到重複請求的場景,如果系統不對重複的請求進行處理,則可能會導致系統出現各種問題。比如重複的 post 請求可能會導致服務端產生兩筆記錄。那麼重複請求是如何產生的呢?這裡我們舉 2 個常見的場景:
- 假設頁面中有一個按鈕,使用者點選按鈕後會發起一個 AJAX 請求。如果未對該按鈕進行控制,當用戶快速點選按鈕時,則會發出重複請求。
- 假設在考試結果查詢頁面中,使用者可以根據 “已通過”、“未通過” 和 “全部” 3 種查詢條件來查程式設計客棧詢考試結果。如果請求的響應比較慢,當用戶在不同的查詢條件之前快速切換時,就會產生重複請求。
既然已經知道重複請求是如何產生的,也知道了它會帶來一些問題。接下來,阿寶哥將以 Axios 為例,帶大家來一起解決重複請求的問題。
一、如何取消請求
Axios 是一個基於 Promise 的 HTTP 客戶端,同時支援瀏覽器和 Node.js 環境。它是一個優秀的 HTTP 客戶端,被廣泛地應用在大量的 Web 專案中。對於瀏覽器環境來說,Axios 底層是利用 XMLHttpRequest 物件來發起 HTTP 請求。如果要取消請求的話,我們可以通過呼叫 XMLHttpRequest 物件上的 abort 方法來取消請求:
let xhr = new XMLHttpRequest(); xhr.open("GET","https://developer.mozilla.org/",true); xhr.send(); setTimeout(() => xhr.abort(),300);
而對於 Axios 來說,我們可以通過 Axios 內部提供的 CancelToken 來取消請求:
const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios.post('/user/12345',{ name: 'semlinker' },{ cancelToken: source.token }) source.cancel('Operation canceled by the user.'); // 取消請求,引數是可選的
此外,你也可以通過呼叫 CancelToken 的建構函式來建立 CancelToken,具體如下所示:
const CancelToken = axios.CancelToken;
let cancel;
axiohttp://www.cppcns.coms.get('/user/12345',{
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
});
cancel(); // 取消請求
現在我們已經知道在 Axios 中如何使用 CancelToken 來取消請求了,那麼 CancelToken 內部是如何工作的呢?這裡我們先記住這個問題,後面阿寶哥將為你們揭開 CancelToken 背後的祕密。接下來,我們來分析一下如何判斷重複請求。
二、如何判斷重複請求
當請求方式、請求 URL 地址和請求引數都一樣時,我們就可以認為請求是一樣的。因此在每次發起請求時,我們就可以根據當前請求的請求方式、請求 URL 地址和請求引數來生成一個唯一的 key,同時為每個請求建立一個專屬的 CancelToken,然後把 key 和 cancel 函式以鍵值對的形式儲存到 Map 物件中,使用 Map 的好處是可以快速的判斷是否有重複的請求:
import qs from 'qs' const pendingRequest = new Map(); // GET -> params;POST -> data const requestKey = [method,url,qs.stringify(params),qs.stringify(data)].join('&'); const cancelToken = new CancelToken(function executor(cancel) { if(!pendingRequest.has(requestKey)){ pendingRequest.set(requestKey,cancel); } })
當出現重複請求的時候,我們就可以使用 cancel 函式來取消前面已經發出的請求,在取消請求之後,我們還需要把取消的請求從 pendingRequest 中移除。現在我們已經知道如何取消請求和如何判斷重複請求,下面我們來介紹如何取消重複請求。
三、如何取消重複請求
因為我們需要對所有的請求都進行處理,所以我們可以考慮使用 Axios 的攔截器機制來實現取消重複請求的功能。Axios 為開發者提供了請求攔截器和響應攔截器,它們的作用如下:
- 請求攔截器:該類攔截器的作用是在請求傳送前統一執行某些操作,比如在請求頭中新增 token 欄位。
- 響應攔截器:該類攔截器的作用是在接收到伺服器響應後統一執行某些操作,比如發現響應狀態碼為 401 時,自動跳轉到登入頁。
3.1 定義輔助函式
在配置請求攔截器和響應攔截器前,阿寶哥先來定義 3 個輔助函式:
generateReqKey:用於根據當前請求的資訊,生成請求 Key; function generateReqKey(config) { const { method,params,data } = config; return [method,Qs.stringify(params),Qs.stringify(data)].join("&"); }
addPendingRequest:用於把當前請求資訊新增到pendingRequest物件中;
const pendingRequest = new Map(); function addPendingRequest(config) { const requestKey = generateReqKey(config); config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => { if (!pendingRequest.has(requestKey)) { pendingRequest.set(requestKey,cancel); } }); }
removePendingRequest:檢查是否存在重複請求,若存在則取消已發的請求。
function removePendingRequest(config) { const requestKey = generateReqKmsDsyxey(config); if (pendingRequest.has(requestKey)) { const cancelToken = pendingRequest.get(requestKey); cancelToken(requestKey); pendingRequest.delete(requestKey); } }
建立好 generateReqKey、addPendingRequest 和 removePendingRequest 函式之後,我們就可以設定請求攔截器和響應攔截器了。
3.2 設定請求攔截器
axios.interceptors.request.use( function (config) { removePendingRequest(config); // 檢查是否存在重複請求,若存在則取消已發的請求 addPendingRequest(config); // 把當前請求資訊新增到pendingRequest物件中 return config; },(error) => { return Promise.reject(error); } );
3.3 設定響應攔截器
axios.interceptors.response.use( (response) => { removePendingRequest(response.config); // 從pendingRequest物件中移除請求 return response; },(error) => { removePendingRequest(error.config || {}); // 從pendingRequest物件中移除請求 if (axios.isCancel(error)) { console.log("已取消的重複請求:" + error.message); } else { // 新增異常處理 } return Promise.reject(error); } );
由於完整的示例程式碼內容比較多,阿寶哥就不放具體的程式碼了。感興趣的小夥伴,可以訪問以下地址瀏覽示例程式碼。
完整的示例程式碼:https://gist.github.com/semlinker/e426780664f0186db434882f1e27ac3a
這裡我們來看一下 Axios 取消重複請求示例的執行結果:
從上圖可知,當出現重複請求時,之前已傳送且未完成的請求會被取消掉。下面我們用一張流程圖來總結一下取消重複請求的處理流程:
最後,我們來回答前面留下的問題,即 CancelToken 內部是如何工作的?
四、CancelToken 的工作原理
在前面的示例中,我們是通過呼叫 CancelToken 建構函式來建立 CancelToken 物件:
new axios.CancelToken((cancel) => { if (!pendingRequest.has(requestKey)) { pendingRequest.set(requestKey,cancel); } })
所以接下來,我們來分析 CancelToken 建構函式,該函式被定義在 lib/cancel/CancelToken.js 檔案中:
// lib/cancel/CancelToken.js
function CancelToken(executor) {
if (typeof executor !== www.cppcns.com'function') {
throw new TypeError('executor must be a function.');
}
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
executor(function cancel(message) { // 設定cancel物件
if (token.reason) {
return; // Cancellation has already been requested
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
由以上程式碼可知,cancel 物件是一個函式,當我們呼叫該函式後,會建立 Cancel 物件並呼叫 resolvePromise 方法。該方法執行後,CancelToken 物件上 promise 屬性所指向的 promise 物件的狀態將變為 resolved。那麼這樣做的目的是什麼呢?這裡我們從 lib/adapters/xhr.js 檔案中找到了答案:
// lib/adapters/xhr.js if (config.cancelToken) { config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); // 取消請求 reject(cancel); msDsyxrequest = null; }); }
看完上述的內容,可能有的小夥伴還不是很能理解 CancelToken 的工作原理,所以阿寶哥又畫了一張圖來幫助大家理解 CancelToken 的工作原理:
五、總結
本文介紹了在 Axios 中如何取消重複請求及 CancelToken 的工作原理,在後續的文章中,阿寶哥將會介紹在 Axios 中如何設定資料快取,感興趣的小夥伴不要錯過喲。如果你想了解 Axios 中 HTTP 攔截器及 HTTP 介面卡的設計與實現,可以閱讀77.9K 的 Axios 專案有哪些值得借鑑的地方 這篇文章。
到此這篇關於Axios取消重複請求的文章就介紹到這了,更多相關Axios取消重複請求內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!
六、參考資源
- Github - Axios
- MDN - XMLHttpRequest
- 77.9K 的 Axios 專案有哪些值得借鑑的地方