用30行代碼封裝一個工具,解決Promise的多并發(fā)問題
背景
提起控制并發(fā),大家應(yīng)該不陌生,我們可以先來看看多并發(fā),再去聊聊為什么要去控制它。
多并發(fā)一般是指多個異步操作同時進行,而運行的環(huán)境中資源是有限的,短時間內(nèi)過多的并發(fā),會對所運行的環(huán)境造成很大的壓力,比如前端的瀏覽器,后端的服務(wù)器,常見的多并發(fā)操作有:
- 前端的多個接口同時請求
- 前端多條數(shù)據(jù)異步處理
- Nodejs的多個數(shù)據(jù)操作同時進行
- Nodejs對多個文件同時進行修改
圖片
正是因為多并發(fā)會造成壓力,所以我們才需要去控制他,降低這個壓力~,比如我可以控制最大并發(fā)數(shù)是 3,這樣的話即使有100個并發(fā),我也能保證最多同時并發(fā)的最大數(shù)量是 3。
圖片
代碼實現(xiàn)
實現(xiàn)思路
大致思路就是,假設(shè)現(xiàn)在有 9 個并發(fā),我設(shè)置最大并發(fā)為 3,那么我將會走下面這些步驟:
1、先定好三個坑位
2、讓前三個并發(fā)進去坑位執(zhí)行
3、看哪個坑位并發(fā)先執(zhí)行完,就從剩余的并發(fā)中拿一個進去補坑
4、一直重復(fù)第 3 步,一直到所有并發(fā)執(zhí)行完
圖片
Promise.all
在進行多并發(fā)的時候,我們通常會使用Promise.all,但是Promise.all并不能控制并發(fā),或者說它本來就沒這個能力,我們可以看下面的例子:
圖片
最后是同時輸出,這說明這幾個并發(fā)是同時發(fā)生的。
圖片
所以我們需要做一些改造,讓Promise.all執(zhí)行 promises 時支持控制并發(fā),但是我們改造的不應(yīng)該是Promise.all,而是這一個個的fetchFn。
期望效果
圖片
圖片
實現(xiàn) limitFn
我們需要在函數(shù)內(nèi)部維護兩個變量:
- queue:隊列,用來存每一個改造過的并發(fā)
- activeCount:用來記錄正在執(zhí)行的并發(fā)數(shù)
并聲明函數(shù) generator ,這個函數(shù)返回一個 Promise,因為 Promise.all 最好是接收一個 Promise 數(shù)組。
圖片
接下來我們來實現(xiàn) enqueue 這個函數(shù)做兩件事:
- 將每一個 fetchFn 放進隊列里
- 將坑位里的 fetchFn 先執(zhí)行
圖片
假如我設(shè)置最大并發(fā)數(shù)為 2,那么這一段代碼在一開始的時候只會執(zhí)行 2 次,因為一開始只會有 2 次符合 if 判斷,大家可以思考一下為什么!
圖片
一開始執(zhí)行 2 次,說明這時候兩個坑位已經(jīng)各自有一個 fetchFn 在執(zhí)行了。
接下來我們實現(xiàn) run 函數(shù),這個函數(shù)是用來包裝 fetch 的,他完成幾件事情:
1、將 activeCount++ ,這時候執(zhí)行中的并發(fā)數(shù) +1
2、將 fetchFn 執(zhí)行,并把結(jié)果 resolve 出去,說明這個并發(fā)執(zhí)行完了
3、將 activeCount--,這時候執(zhí)行中的并發(fā)數(shù) -1
4、從 queue 中取一個并發(fā),拿來補坑執(zhí)行
圖片
其實第 3、4 步,是在 next 函數(shù)里面執(zhí)行的。
圖片
完整代碼
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;
};
這不是我寫的
其實這是一個很出名的庫的源碼,就是p-limit,哈哈,但是重要嗎?知識嘛,讀懂了,它就是你的,到時跟面試官嘮嗑的時候,他哪知道是不是真的是你寫的!