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