axios++:防止重複提交全域性統一攔截
阿新 • • 發佈:2021-10-14
防重提交是個老生常談的問題,使用外部變數鎖定或修改按鈕狀態的方式方式比較繁瑣冗餘,
而知乎的哥們在怎樣防止重複傳送 Ajax 請求?的問答上,提到了防重提交的幾個方式,
根據實際專案的需求,採用了A. 獨佔型提交
+D. 懶惰型提交
組合方式,程式碼實現如下:
// http.js import { debounce } from "../debounce"; let pendingArr = []; let CancelToken = axios.CancelToken; let pendingHandler = (flag, cancelFn) => { if (pendingArr.indexOf(flag) > -1) {if (cancelFn) { cancelFn(); // cancel } else { pendingArr.splice(pendingArr.indexOf(flag), 1); // remove flag } } else { if (cancelFn) { pendingArr.push(flag); } } }; // request interceptor axios.interceptors.request.use( config=> { config.cancelToken = new CancelToken(cancelFn => { pendingHandler(config.baseURL + config.url + "&" + config.method, cancelFn); }); return config; }, err => { return Promise.reject(err); } ); // response interceptor axios.interceptors.response.use( response=> { pendingHandler(response.config.url + "&" + response.config.method); return response; }, err => { pendingArr = []; return Promise.reject(err); } ); return debounce( axios(config) .then(response => { // handle response resolve(response.data) }) .catch(thrown => { if (axios.isCancel(thrown)) { console.log("Request canceled", thrown.message); } else { let { response, request, message } = thrown; reject(message); } }), 500, true );
// debounce.js export function debounce(func, wait, immediate) { var timeout, args, context, timestamp, result; if (null == wait) wait = 100; function later() { var last = Date.now() - timestamp; if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); context = args = null; } } } var debounced = function() { context = this; args = arguments; timestamp = Date.now(); var callNow = immediate && !timeout; if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; debounced.clear = function() { if (timeout) { clearTimeout(timeout); timeout = null; } }; debounced.flush = function() { if (timeout) { result = func.apply(context, args); context = args = null; clearTimeout(timeout); timeout = null; } }; return debounced; }
這裡用到了 axios 攔截器,初始化一個pendingArr
陣列,用於存放正在 pending 的請求,以url
+method
為標誌,
在 http response 攔截器刪除完成 pending 的請求,在 http request 的攔截器中,先判斷是否存在正在 pending 的同一個請求,若無則把請求標誌位存入陣列,若存在,則取消請求。
到這裡還未結束,前面做的是終止請求,但是請求已經發出,短時間內頻繁請求,會對伺服器造成一定壓力,
所以我這裡用了一個debounce (防抖動)
,規定時間內把觸發非常頻繁的事件合併成一次執行,比如我這裡是500ms 內,觸發很頻繁的請求都會合併成一次執行,避免使用者瘋狂點選,觸發多次請求的情況。
至於debounce
的實現,我這裡是摘取underscore
原始碼,至於原理可以參考Debouncing and Throttling Explained Through Examples,這裡闡述了debounce (防抖動)
、throttling(節流閥)
的原理及其異同。
簡化版:
let pending = []; //宣告一個數組用於儲存每個ajax請求的取消函式和ajax標識 let cancelToken = axios.CancelToken; let removePending = (config) => { for(let p in pending){ if(pending[p].u === config.url + '&' + config.method) { //噹噹前請求在陣列中存在時執行函式體 pending[p].f(); //執行取消操作 pending.splice(p, 1); //把這條記錄從陣列中移除 } } } //新增請求攔截器 axios.interceptors.request.use(config=>{ removePending(config); //在一個ajax傳送前執行一下取消操作 config.cancelToken = new cancelToken((c)=>{ // 這裡的ajax標識我是用請求地址&請求方式拼接的字串,當然你可以選擇其他的一些方式 pending.push({ u: config.url + '&' + config.method, f: c }); }); return config; },error => { return Promise.reject(error); }); //新增響應攔截器 axios.interceptors.response.use(response=>{ removePending(res.config); //在一個ajax響應後再執行一下取消操作,把已經完成的請求從pending中移除 return response; },error =>{ return { data: { } }; 返回一個空物件,否則控制檯報錯 });