Promise.all概念
首先了解一下Promise.all,
Promise.all可以將多個Promise實例包裝成一個新的Promise實例。同時,成功和失敗的返回值是不同的,成功的時候返回的是一個結果數組,而失敗的時候則返回最先被reject失敗狀態的值(第一次失敗就返回了,而不等待后續promise的狀態)。
Promise.all 可以保證,promises 數組中所有promise對象都達到 resolve 狀態,才執行 then 回調,而如果是有一個reject了,那就直接執行catch的代碼。
注意Promises數組的執行並不是通過Promise.all而是在我們初始化Promise的時候就執行了。可以看一下下面這個情況:
let a = new Promise((resolve,eject)=>{ console.log(123); setTimeout(()=>resolve("Promise a"),1000) }) let b = new Promise((resolve,eject)=>{ console.log(222); setTimeout(()=>resolve("Promise b"),5000) }) Promise.all([a,b]).then((res)=>console.log(res)); Promise.all([a,b]).then((res)=>console.log(res));
輸出結果為
相當於,a和b兩個promise在創建的時候執行,我們只是通過Promise.all拿到結果而已,多次執行Promise.all只是多次獲取到結果,並不會執行promise數組。
Promise.all並發限制實現
⛲️ 場景:如果你都的promises 數組中每個對象都是http請求,你在瞬間發出幾十萬http請求(tcp連接數不足可能造成等待),或者堆積了無數調用棧導致內存溢出。這時,就需要考慮對Promise.all做並發限制。
Promise.all並發限制指的是,每個時刻並發執行的promise數量是固定的,最終的執行結果還是保持與原來的Promise.all一致。
首先先看一下要實現的效果,有一個定時器,在1000,5000,3000,2000s之后輸出時間,每次並發為2。也就是先執行1000和5000,等到1000完成后,開始執行3000,以此類推直到所有任務完成(先實現這樣一個簡單的並發控制,並沒有對promise數組拿到結果,執行then)
const timeout = i => new Promise(resolve => { console.log(i); setTimeout(() => resolve(i), i) }); asyncPools(2, [1000, 5000, 3000, 2000], timeout)
1. asyncPools 接受三個參數(poolLimit, array, iteratorFn
)
poolLimit:並發限制
array:需要執行的並發數組
iteratorFn:具體執行的promise函數
function asyncPools(poolLimit,array,fnInter) { let doing = []; let i =0; function pp(){ if(i>=array.length) { return; } let e = fnInter(array[i++]); //初始化promise e.then(()=>doing.splice(doing.indexOf(e),1)) //完成之后從doing刪除 doing.push(e); //放進doing列表 if(doing.length>=poolLimit) { //超出限制的話 Promise.race(doing).then(pp); //監聽每完成一個就再放一個 } else { //否則直接放進去 pp(); } } pp(); }
思路:首先需要一個doing數組記錄正在執行的promise,因為需要不停的初始化並且放入doing數組,所以采用遞歸的形式。有一個變量i記錄初始化到array的第幾個了。---> 開始從array中初始化promise並且放入doing數組,因為promise完成之后需要從doing中刪除,所以注冊這個操作到初始化的promise中。--->那么下一次再初始化放入的時機就由poolLimit來決定,超出限制的話,使用promise.race來監聽doing列表有一個promise完成,就放入;沒有超出限制,直接放入。--->當array列表執行完畢也就是i>=array.length的時候結束操作。
2. 接下來是保存每一個promise狀態,在使用了並發asyncPools函數可以通過他的then方法拿到結果。所以我們對上面代碼進行了改進,增加一個ret來保存每一個初始化的promise,最后返回Promise.all(ret)拿到promise數組的結果。
使用示例:
asyncPools(2, [1000, 5000, 3000, 2000], timeout).then(res=>console.log(res));
第一步:存儲Promise數組,返回Promise.all(ret);
function asyncPools(poolLimit,array,fnInter) { let doing = []; let i =0; let ret = []; //結果數組 function pp(){ if(i>=array.length) { return Promise.all(ret); //返回結果 } let e = fnInter(array[i++]); e.then(()=>doing.splice(doing.indexOf(e),1)) doing.push(e); ret.push(e); //放入ret數組 if(doing.length>=poolLimit) { Promise.race(doing).then(pp); } else { pp(); } } pp(); }
但是直接使用會報錯,因為內部是異步的,執行完之后拿到的結果其實是undefined,沒有then方法。
第二步:改寫函數的return,使得返回的是promise對象可以實現then的鏈式調用
function asyncPools(poolLimit,array,fnInter) { let doing = []; let i =0; let ret = []; function pp(){ if(i>=array.length) { return Promise.resolve(); //最后一個resolve狀態,會進入外層返回Promise.then } let e = fnInter(array[i++]); e.then(()=>doing.splice(doing.indexOf(e),1)) doing.push(e); ret.push(e); if(doing.length>=poolLimit) { return Promise.race(doing).then(pp); //return返回 } else { return Promise.resolve().then(pp); //改寫一下保證then鏈式調用 } } return pp().then(()=>Promise.all(ret)); //只有當array結束,最后一個resolve才會進入then }
至此我們的並發限制代碼就完成了!
下面的內容就是介紹一下和Promise.all經常一起出現的概念Promise.race了!
Promise.race(iterable)
方法返回一個 promise,一旦迭代器中的某個promise解決或拒絕,返回的 promise就會解決或拒絕。