本文是深入淺出 ahooks 原始碼系列文章的第四篇,該系列已整理成檔案-地址,覺得還不錯,給個 star 支持一下哈,Thanks,
本文來探索一下 ahooks 的 useLockFn,并由此討論一個很常見的場景,取消重復請求,
場景
試想一下,有這么一個場景,有一個表單,你可能多次提交,就很可能導致結果不正確,
解決這類問題的方法有很多,比如添加 loading,在第一次點擊之后就無法再次點擊,另外一種方法就是給請求異步函式添加上一個靜態鎖,防止并發產生,這就是 ahooks 的 useLockFn 做的事情,
useLockFn
useLockFn 用于給一個異步函式增加競態鎖,防止并發執行,
它的原始碼比較簡單,如下所示:
import { useRef, useCallback } from 'react';
// 用于給一個異步函式增加競態鎖,防止并發執行,
function useLockFn<P extends any[] = any[], V extends any = any>(fn: (...args: P) => Promise<V>) {
// 是否現在處于一個鎖中
const lockRef = useRef(false);
// 回傳的是增加了競態鎖的函式
return useCallback(
async (...args: P) => {
// 判斷請求是否正在進行
if (lockRef.current) return;
// 請求中
lockRef.current = true;
try {
// 執行原有請求
const ret = await fn(...args);
// 請求完成,狀態鎖設定為 false
lockRef.current = false;
return ret;
} catch (e) {
// 請求失敗,狀態鎖設定為 false
lockRef.current = false;
throw e;
}
},
[fn],
);
}
export default useLockFn;
可以看到,它的入參是異步函式,回傳的是一個增加了競態鎖的函式,通過 lockRef 做一個標識位,初始化的時候它的值為 false,當正在請求,則設定為 true,從而下次再呼叫這個函式的時候,就直接 return,不執行原函式,從而達到加鎖的目的,
缺點
雖然實用,但缺點很明顯,我需要給每一個需要添加競態鎖的請求異步函式都手動加一遍,那有沒有比較通用和方便的方法呢?
答案是可以通過 axios 自動取消重復請求,
axios 自動取消重復請求
axios 取消請求
對于原生的 XMLHttpRequest 物件發起的 HTTP 請求,可以呼叫 XMLHttpRequest 物件的 abort 方法,
那么我們專案中常用的 axios 呢?它其實底層也是用的 XMLHttpRequest 物件,它對外暴露取消請求的 API 是 CancelToken,可以使用如下:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.post('/user/12345', {
name: 'gopal'
}, {
cancelToken: source.token
})
source.cancel('Operation canceled by the user.'); // 取消請求,引數是可選的
另外一種使用的方法是呼叫 CancelToken 的建構式來創建 CancelToken,具體使用如下:
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
});
cancel(); // 取消請求
如何自動取消重復的請求
知道了如何取消請求,那怎么做到自動取消呢?答案是通過 axios 的攔截器,
- 請求攔截器:該類攔截器的作用是在請求發送前統一執行某些操作,比如在請求頭中添加 token 相關的欄位,
- 回應攔截器:該類攔截器的作用是在接收到服務器回應后統一執行某些操作,比如發現回應狀態碼為 401 時,自動跳轉到登錄頁,
具體的做法如下:
第一步,定義幾個重要的輔助函式,
generateReqKey:用于根據當前請求的資訊,生成請求 Key,只有 key 相同才會判定為是重復請求,這一點很重要,而且可能跟具體的業務場景有關,比如有一種請求,輸入框模糊搜索,用戶高頻輸入關鍵字,一次性發出多個請求,可能先發出的請求,最后才回應,導致實際搜索結果與預期不符,這種其實就只需要根據 URL 和請求方法判定其為重復請求,然后取消之前的請求就可以了,
這里我認為,如果有需要的話,可以暴露一個 API 給開發者進行自定義重復的規則,這里我們先根據請求方法、url、以及引數生成唯一的 key 去做,
function generateReqKey(config) {
const { method, url, params, data } = config;
return [method, url, 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 = generateReqKey(config);
if (pendingRequest.has(requestKey)) {
const cancelToken = pendingRequest.get(requestKey);
cancelToken(requestKey);
pendingRequest.delete(requestKey);
}
}
第二步,添加請求攔截器,
axios.interceptors.request.use(
function (config) {
removePendingRequest(config); // 檢查是否存在重復請求,若存在則取消已發的請求
addPendingRequest(config); // 把當前請求資訊添加到pendingRequest物件中
return config;
},
(error) => {
return Promise.reject(error);
}
);
第二步,添加回應攔截器,
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);
}
);
到這一步,我們就通過 axios 完成了自動取消重復請求的功能,
思考與總結
雖然可以通過類似 useLockFn 這樣的 hook或方法給請求函式添加競態鎖的方式解決重復請求的問題,但這種還是需要依賴于開發者的習慣,如果沒有一些規則的約束,很難避免問題,
通過 axios 攔截器以及其 CancelToken 功能,我們能夠在攔截器中自動將已發的請求取消,當然假如有一些介面就是需要重復發送請求,可以考慮加一下白名單功能,讓請求不進行取消,
參考
- Axios 如何取消重復請求?
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/501748.html
標籤:其他
上一篇:實作一個會動的鴻蒙 LOGO
