JavaScript?Promise多并發(fā)問題的解決方法詳解
提起控制并發(fā),大家應(yīng)該不陌生,我們可以先來看看多并發(fā),再去聊聊為什么要去控制它
多并發(fā)一般是指多個(gè)異步操作同時(shí)進(jìn)行,而運(yùn)行的環(huán)境中資源是有限的,短時(shí)間內(nèi)過多的并發(fā),會(huì)對所運(yùn)行的環(huán)境造成很大的壓力,比如前端的瀏覽器,后端的服務(wù)器,常見的多并發(fā)操作有:
- 前端的多個(gè)接口同時(shí)請求
- 前端多條數(shù)據(jù)異步處理
- Nodejs的多個(gè)數(shù)據(jù)操作同時(shí)進(jìn)行
- Nodejs對多個(gè)文件同時(shí)進(jìn)行修改

正是因?yàn)?code>多并發(fā)會(huì)造成壓力,所以我們才需要去控制他,降低這個(gè)壓力~,比如我可以控制最大并發(fā)數(shù)是 3,這樣的話即使有100個(gè)并發(fā),我也能保證最多同時(shí)并發(fā)的最大數(shù)量是 3

代碼實(shí)現(xiàn)
實(shí)現(xiàn)思路
大致思路就是,假設(shè)現(xiàn)在有 9 個(gè)并發(fā),我設(shè)置最大并發(fā)為 3,那么我將會(huì)走下面這些步驟:
- 1、先定好三個(gè)坑位
- 2、讓前三個(gè)并發(fā)進(jìn)去坑位執(zhí)行
- 3、看哪個(gè)坑位并發(fā)先執(zhí)行完,就從剩余的并發(fā)中拿一個(gè)進(jìn)去補(bǔ)坑
- 4、一直重復(fù)第 3 步,一直到所有并發(fā)執(zhí)行完

Promise.all
在進(jìn)行多并發(fā)的時(shí)候,我們通常會(huì)使用Promise.all,但是Promise.all并不能控制并發(fā),或者說它本來就沒這個(gè)能力,我們可以看下面的例子
const fetchFn = (delay, index) => {
return new Promise(resolve => {
console.log(index)
setTimeout(() => {
resolve(index)
}, delay);
})
}
const promises = [
fetchFn(1000, 1),
fetchFn(1000, 2),
fetchFn(1000, 3),
fetchFn(1000, 4),
fetchFn(1000, 5),
fetchFn(1000, 6)
]
Promise.all(promises)最后是同時(shí)輸出,這說明這幾個(gè)并發(fā)是同時(shí)發(fā)生的

所以我們需要做一些改造,讓Promise.all執(zhí)行 promises 時(shí)支持控制并發(fā),但是我們改造的不應(yīng)該是Promise.all,而是這一個(gè)個(gè)的fetchFn
期望效果
const limitFn = (limit) => {
// ...coding
}
// 最大并發(fā)數(shù) 2
const generator = limitFn(2)
const promises = [
generator(() => fetchFn(1000, 1)),
generator(() => fetchFn(1000, 2)),
generator(() => fetchFn(1000, 3)),
generator(() => fetchFn(1000, 4)),
generator(() => fetchFn(1000, 5)),
generator(() => fetchFn(1000, 6))
]
Promise.all(promises)
實(shí)現(xiàn) limitFn
我們需要在函數(shù)內(nèi)部維護(hù)兩個(gè)變量:
- queue:隊(duì)列,用來存每一個(gè)改造過的并發(fā)
- activeCount:用來記錄正在執(zhí)行的并發(fā)數(shù)
并聲明函數(shù) generator ,這個(gè)函數(shù)返回一個(gè) Promise,因?yàn)?Promise.all 最好是接收一個(gè) Promise 數(shù)組
const limitFn = (concurrency) => {
const queue = [];
let activeCount = 0;
const generator = (fn, ...args) =>
new Promise((resolve) => {
enqueue(fn, resolve, ...args);
});
return generator;
};接下來我們來實(shí)現(xiàn) enqueue 這個(gè)函數(shù)做兩件事:
- 將每一個(gè) fetchFn 放進(jìn)隊(duì)列里
- 將坑位里的 fetchFn 先執(zhí)行
const enqueue = (fn, resolve, ...args) => {
queue.push(run.bind(null, fn, resolve, ...args));
if (activeCount < limit && queue.length > 0) {
queue.shift()();
}
};假如我設(shè)置最大并發(fā)數(shù)為 2,那么這一段代碼在一開始的時(shí)候只會(huì)執(zhí)行 2 次,因?yàn)橐婚_始只會(huì)有 2 次符合 if 判斷,大家可以思考一下為什么~
if (activeCount < limit && queue.length > 0) {
queue.shift()(); // 這段代碼
}一開始執(zhí)行 2 次,說明這時(shí)候兩個(gè)坑位已經(jīng)各自有一個(gè) fetchFn 在執(zhí)行了
接下來我們實(shí)現(xiàn) run 函數(shù),這個(gè)函數(shù)是用來包裝 fetch 的,他完成幾件事情:
- 1、將 activeCount++ ,這時(shí)候執(zhí)行中的并發(fā)數(shù) +1
- 2、將 fetchFn 執(zhí)行,并把結(jié)果 resolve 出去,說明這個(gè)并發(fā)執(zhí)行完了
- 3、將 activeCount--,這時(shí)候執(zhí)行中的并發(fā)數(shù) -1
- 4、從 queue 中取一個(gè)并發(fā),拿來補(bǔ)坑執(zhí)行
const run = async (fn, resolve, ...args) => {
activeCount++;
const result = (async () => fn(...args))();
try {
const res = await result;
resolve(res);
} catch { }
next();
};其實(shí)第 3、4 步,是在 next 函數(shù)里面執(zhí)行的
const next = () => {
activeCount--;
if (queue.length > 0) {
queue.shift()();
}
};完整代碼
const limitFn = (limit) => {
const queue = [];
let activeCount = 0;
const next = () => {
activeCount--;
if (queue.length > 0) {
queue.shift()();
}
};
const run = async (fn, resolve, ...args) => {
activeCount++;
const result = (async () => fn(...args))();
try {
const res = await result;
resolve(res);
} catch { }
next();
};
const enqueue = (fn, resolve, ...args) => {
queue.push(run.bind(null, fn, resolve, ...args));
if (activeCount < limit && queue.length > 0) {
queue.shift()();
}
};
const generator = (fn, ...args) =>
new Promise((resolve) => {
enqueue(fn, resolve, ...args);
});
return generator;
};這不是我寫的
其實(shí)這是一個(gè)很出名的庫的源碼,就是p-limit,哈哈,但是重要嗎?知識(shí)嘛,讀懂了,它就是你的,到時(shí)跟面試官嘮嗑的時(shí)候,他哪知道是不是真的是你寫的~
到此這篇關(guān)于JavaScript Promise多并發(fā)問題的解決方法詳解的文章就介紹到這了,更多相關(guān)JavaScript Promise內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript開發(fā)隨筆二 動(dòng)態(tài)加載js和文件
js無非就是script標(biāo)簽引入頁面,但當(dāng)項(xiàng)目越來越大的時(shí)候,單頁面引入N個(gè)js顯然不行,合并為單個(gè)文件減少了請求數(shù),但請求的文件體積卻很大2011-11-11
webpack的 rquire.context用法實(shí)現(xiàn)工程自動(dòng)化的方法
這篇文章主要介紹了webpack的 rquire.context用法實(shí)現(xiàn)工程自動(dòng)化的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
js如何計(jì)算斐波那契數(shù)列第n項(xiàng)的值
這篇文章主要介紹了js如何計(jì)算斐波那契數(shù)列第n項(xiàng)的值問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
JavaScript中文件緩存導(dǎo)致404錯(cuò)誤的解決方案
當(dāng) JavaScript 文件在服務(wù)器上被更新或刪除后,瀏覽器仍然請求舊的緩存文件,導(dǎo)致 404 錯(cuò)誤,本文將深入探討這一問題的原因,并提供多種解決方案,需要的可以參考下2025-03-03
javascript頁面上使用動(dòng)態(tài)時(shí)間具體實(shí)現(xiàn)
這篇文章主要介紹了javascript在頁面上使用動(dòng)態(tài)時(shí)間實(shí)現(xiàn)示例,需要的朋友可以參考下2014-03-03
JS檢測頁面中哪個(gè)HTML標(biāo)簽觸發(fā)點(diǎn)擊事件的方法
這篇文章主要介紹了JS檢測頁面中哪個(gè)HTML標(biāo)簽觸發(fā)點(diǎn)擊事件的方法,涉及javascript頁面元素事件響應(yīng)機(jī)制,需要的朋友可以參考下2016-06-06
javascript打開新窗口同時(shí)關(guān)閉舊窗口
因業(yè)務(wù)需要,在網(wǎng)上查找這個(gè)問題的解決辦法,但是昏天黑地地搞了半天,找到的方法雖然可以實(shí)現(xiàn)功能,但是總是會(huì)跳出討厭的“關(guān)閉窗口”的提示框,郁悶。2009-01-01

