前端請(qǐng)求并發(fā)和請(qǐng)求覆蓋的解決方法
頁面太多接口并發(fā)請(qǐng)求會(huì)出現(xiàn)什么問題?
- 服務(wù)器壓力會(huì)變大:大量的并發(fā)請(qǐng)求會(huì)導(dǎo)致服務(wù)器的負(fù)載增加,從而影響服務(wù)器的性能和穩(wěn)定性。
- 網(wǎng)絡(luò)擁堵:一個(gè)域名最多有 6 個(gè)并發(fā)請(qǐng)求(不同瀏覽器可能限制不一樣),超過 6 個(gè)并發(fā)請(qǐng)求會(huì)導(dǎo)致網(wǎng)絡(luò)擁堵,從而影響頁面的加載速度和用戶體驗(yàn)。
- 響應(yīng)延遲:由于服務(wù)器需要處理大量的并發(fā)請(qǐng)求,所以響應(yīng)延遲會(huì)增加,從而影響頁面的響應(yīng)速度和用戶體驗(yàn)。
解決方式也有很多,比如:
- 負(fù)載均衡:分發(fā)請(qǐng)求到多個(gè)服務(wù)器上
- 聚合接口:將多個(gè)接口合并成一個(gè)接口,減少接口的并發(fā)請(qǐng)求
- CDN 內(nèi)容分發(fā):通過不同的域名進(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ò)誤并記錄。無論成功或失敗,最終都會(huì)調(diào)用 finally 塊來減少當(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);
});
},
// 生成測試請(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)求覆蓋
場景:先后有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ì)列來維護(hù)請(qǐng)求的順序,按照隊(duì)列的順序發(fā)起請(qǐng)求,但這有種“殺雞用牛刀”的感覺,因?yàn)槲覀兺耆梢匀∠暗恼?qǐng)求,用最新的請(qǐng)求結(jié)果來賦值
可以通過以下方式解決請(qǐng)求覆蓋的問題:
- 時(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)求
// 通過axios的cancelToken來取消請(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é)【二】列表分頁處理和插件JSTree的使用
本文給大家介紹基于BootStrap Metronic開發(fā)框架經(jīng)驗(yàn)小結(jié)【二】列表分頁處理和插件JSTree的使用,介紹頁面內(nèi)容常用到的數(shù)據(jù)分頁處理,以及Bootstrap插件JSTree的使用,非常具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-05-05
javascript實(shí)現(xiàn)獲取服務(wù)器時(shí)間
本文給大家總結(jié)了一下使用javascript來獲取服務(wù)器時(shí)間的幾種方法和思路,十分的簡單明了,有需要的小伙伴可以參考下2015-05-05
ES6中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)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
url特殊字符編碼encodeURI?VS?encodeURIComponent分析
這篇文章主要介紹了url特殊字符編碼encodeURI?VS?encodeURIComponent分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09

