前端請求并發(fā)和請求覆蓋的解決方法
頁面太多接口并發(fā)請求會出現(xiàn)什么問題?
- 服務器壓力會變大:大量的并發(fā)請求會導致服務器的負載增加,從而影響服務器的性能和穩(wěn)定性。
- 網(wǎng)絡擁堵:一個域名最多有 6 個并發(fā)請求(不同瀏覽器可能限制不一樣),超過 6 個并發(fā)請求會導致網(wǎng)絡擁堵,從而影響頁面的加載速度和用戶體驗。
- 響應延遲:由于服務器需要處理大量的并發(fā)請求,所以響應延遲會增加,從而影響頁面的響應速度和用戶體驗。
解決方式也有很多,比如:
- 負載均衡:分發(fā)請求到多個服務器上
- 聚合接口:將多個接口合并成一個接口,減少接口的并發(fā)請求
- CDN 內(nèi)容分發(fā):通過不同的域名進行請求,從而突破瀏覽器單個域名并發(fā)請求限制
以上都是基于運維和后端的角度,那么前端如何解決呢?
并發(fā)請求
思路:設置并發(fā)請求限制,用隊列存放請求,每次請求會先判斷是否超出設置的最大并發(fā)請求數(shù),當請求完成后,從隊列中取出下一個請求,直到隊列中的請求全部完成。
export class RequestQueue { constructor(concurrency = 6) { this.concurrency = concurrency; // 設置最大并發(fā)數(shù),默認為6 this.queue = []; // 存放請求的隊列 this.current = 0; // 當前正在執(zhí)行的請求數(shù) } // 處理隊列中的請求(出隊) dequeue() { while (this.current < this.concurrency && this.queue.length) { this.current++; // 從隊列中取出下一個請求并執(zhí)行 const requestPromiseFactory = this.queue.shift(); requestPromiseFactory() .then(() => { // 成功的請求邏輯 }) .catch((error) => { // 失敗 console.log(error); }) .finally(() => { this.current--; this.dequeue(); }); } } // 添加請求到隊列中(入隊) enqueue(requestPromiseFactory) { this.queue.push(requestPromiseFactory); this.dequeue(); } }
代碼解釋:
- 構(gòu)造函數(shù) (constructor):初始化了并發(fā)數(shù) (concurrency)、請求隊列 (queue) 和當前正在執(zhí)行的請求數(shù)量 (current)。
- 入隊方法 (enqueue):將請求添加到隊列中,并立即調(diào)用 dequeue 方法開始處理隊列。
- 出隊方法 (dequeue):從隊列中取出請求并執(zhí)行。如果請求成功,執(zhí)行成功邏輯;如果請求失敗,捕獲錯誤并記錄。無論成功或失敗,最終都會調(diào)用 finally 塊來減少當前正在執(zhí)行的請求數(shù)量,并繼續(xù)處理下一個請求。
實際使用
const requestQueue = new RequestQueue(6); // 創(chuàng)建一個并發(fā)數(shù)為6的請求隊列 // 模擬一個異步函數(shù) sleep(fn) { return new Promise(resolve => { setTimeout(() => { resolve(fn); }, 2000); }); }, // 生成測試請求 const queue = [...Array(20)].map((_, i) => () => this.sleep( axios .get('/api/test' + i) .then(r => console.log(i, '成功')) .catch(e => console.log('失敗', i)) ) ); // 添加請求到隊列中 for (let i = 0; i < queue.length; i++) { requestQueue.enqueue(queue[i]); }
請求覆蓋
場景:先后有A、B兩個請求,A請求還未返回,B請求已經(jīng)發(fā)起,并且B請求的結(jié)果比A先返回,那么A請求就會覆蓋B請求的結(jié)果,正常要的結(jié)果是B的結(jié)果覆蓋掉A請求的結(jié)果
可以用隊列來維護請求的順序,按照隊列的順序發(fā)起請求,但這有種“殺雞用牛刀”的感覺,因為我們完全可以取消之前的請求,用最新的請求結(jié)果來賦值
可以通過以下方式解決請求覆蓋的問題:
- 時序控制:定全局標識,比如數(shù)字,依次累加,每個請求響應中判斷當前的標識是否 ≥ 全局標識,是則返回結(jié)果,否則不返回結(jié)果。
- 取消舊請求:發(fā)送新請求時判斷是否有舊請求,有則取消舊請求,然后再發(fā)送新請求。
方法一:時序控制
let requestId = 0; // 全局標識 // 發(fā)送請求 function sendRequest() { const currentRequestId = ++requestId; // 遞增全局標識 // 發(fā)起請求 axios.get('/api/data') .then(response => { // 判斷當前請求是否是最新的請求(如果有新的請求那么requestId在新的請求會+1,比當前這個方法的curentRequestId的要大) if (currentRequestId >= requestId) { // 處理響應數(shù)據(jù) console.log(response.data); } }) .catch(error => { // 處理錯誤 console.error(error); }); }
方法二:取消舊請求
// 通過axios的cancelToken來取消請求 let cancelToken; // 取消請求的令牌 // 發(fā)送請求 function sendRequest() { // 取消舊請求 if (cancelToken) { cancelToken.cancel(); } // 創(chuàng)建新的取消請求的令牌 cancelToken = axios.CancelToken.source(); // 發(fā)起請求 axios.get('/api/data', { cancelToken: cancelToken.token }) .then(response => { // 處理響應數(shù)據(jù) console.log(response.data); }) .catch(error => { // 處理錯誤 console.error(error); }); }
// 自定義的取消請求函數(shù) let lastCancel = null; let cancelable = (req, callback) => { let cb = callback; req.then(res => { cb && cb(res); }) let cancel = () => { cb = null; } return cancel; } let sendRequest() { lastCancel && lastCancel(); lastCancel = cancelable(axios.get('/api/data'), res => { console.log(res); }) }
到此這篇關于前端請求并發(fā)和請求覆蓋的解決方法的文章就介紹到這了,更多相關前端請求并發(fā)和請求覆蓋內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
基于BootStrap Metronic開發(fā)框架經(jīng)驗小結(jié)【二】列表分頁處理和插件JSTree的使用
本文給大家介紹基于BootStrap Metronic開發(fā)框架經(jīng)驗小結(jié)【二】列表分頁處理和插件JSTree的使用,介紹頁面內(nèi)容常用到的數(shù)據(jù)分頁處理,以及Bootstrap插件JSTree的使用,非常具有參考借鑒價值,感興趣的朋友一起學習吧2016-05-05ES6中l(wèi)et、const的區(qū)別及變量的解構(gòu)賦值操作方法實例分析
這篇文章主要介紹了ES6中l(wèi)et、const的區(qū)別及變量的解構(gòu)賦值操作方法,結(jié)合實例形式分析了ES6中l(wèi)et、const的功能、原理、使用方法及數(shù)組、字符串、函數(shù)參數(shù)等解構(gòu)賦值相關操作技巧,需要的朋友可以參考下2019-10-10url特殊字符編碼encodeURI?VS?encodeURIComponent分析
這篇文章主要介紹了url特殊字符編碼encodeURI?VS?encodeURIComponent分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09