Promise.all (iterable): The all function returns a new promise which is fulfilled with an array of fulfillment values for the passed promises, or rejects with the reason of the first passed promise that rejects. It resolves all elements of the passed iterable to promises as it runs this algorithm.
Promise.all 靜態方法具備以下特性:數組
下面是 Promise.all 的簡單用法:promise
const p1 = Promise.resolve(1); const p2 = Promise.resolve(2); const p3 = Promise.resolve(3); Promise.all([p1, p2, p3]) .then((results) => { console.log(results); // [1, 2, 3] });
const p1 = Promise.resolve(1); const p2 = Promise.reject(2); const p3 = Promise.resolve(3); Promise.all([p1, p2, p3]) .then((results) => { console.log(results); }).catch((e) => { console.log(e); // 2 });
Promise.all = Promise.all || function(promises) { // 若是實參不是數組則報錯返回 if (!isArray(promises)) { throw new TypeError('You must pass an array to all.'); } // 結果返回一個新的 Promise 實例 return new Promise(function(resolve, reject) { var i = 0, result = [], len = promises.length, count = len // 使用閉包記錄數組執行順序 function resolver(index) { return function(value) { resolveAll(index, value); }; } // 只要有一個失敗狀態就變爲 rejected function rejecter(reason) { reject(reason); } // 若是所有成功狀態變爲 resolved function resolveAll(index, value) { result[index] = value; if (--count == 0) { resolve(result) } } // 遍歷數組併發執行異步代碼 for (; i < len; i++) { promises[i].then(resolver(i), rejecter); } }); }
那麼回到題目的問題:如何實現一個調度粒度可變的 Promise.all 靜態方法呢?這裏首先可能會產生一個疑問就是什麼叫調度粒度可變,實際上很簡單:就是給 all 方法增長一個正整數類型的參數,用來標識傳入的 Promise 實例數組中能夠併發執行的最大個數。
舉例以下,聲明三個不一樣異步時間的 Promise 實例並調用 all 方法,正常狀況下(咱們粗暴的認爲)其執行時間應該爲200ms(固然這是一個錯誤的答案,在這個例子當中咱們僅考慮併發邏輯),而在調度粒度爲1的 all 方法中其執行時間應該爲450ms,在調度粒度爲2的 all 方法中其執行時間應該爲250ms,以此類推。緩存
const p1 = new Promise((resolve) => setTimeout(() => resolve(1), 150)); const p2 = new Promise((resolve) => setTimeout(() => resolve(2), 200)); const p3 = new Promise((resolve) => setTimeout(() => resolve(3), 100)); Promise.all([p1, p2, p3]).then((r) => console.log(r));
在這種狀況下,立刻能夠想到的一個方法是:咱們可使用隊列的數據結構來實現調度粒度爲1的 all 方法。數據結構
Promise.all1 = (promises) => { if (!isArray(promises)) { throw new TypeError('You must pass an array to all.'); } return new Promise((resolve, reject) => { const _q = [...promises]; const result = []; function resolver(value) { result.push(value); next(); } function rejecter(reason) { reject(reason); } function next() { if (_q.length) { _q.shift().then(resolver, rejecter); } else { resolve(result); } } next(); }); }
寫到這兒不難發現,不一樣調度粒度其實是對隊列每次推出的 Promise 實例數量最大值的約束,以及對返回結果的順序索引做出緩存,那麼把代碼進行簡單的修改便可實現預期的功能。閉包
Promise.all2 = (promises, concurrent = promises.length) => { if (!Array.isArray(promises)) { throw new TypeError('You must pass an array to all.'); } if (concurrent < 1) { return Promise.reject(); } return new Promise((resolve, reject) => { const queue = [...promises]; const result = []; let total = promises.length; let count = concurrent; let index = 0; function resolver(index) { return function(value) { resolveAll(index, value); }; } function resolveAll(index, value) { result[index] = value; count++; total--; next(); if (!total) { resolve(result); } } function rejecter(reason) { reject(reason); } function next() { while (queue.length && count > 0) { count--; (queue.shift())().then(resolver(index++), rejecter); } } next(); }); };
固然這裏有一個比較弔詭的地方!若是咱們按照上面的代碼進行 Promise 實例的聲明,那麼在執行到 all 方法以前它們就已經在併發執行,也就不會有「不一樣調度粒度」之說,因此爲了實現咱們預期的功能須要在 all 方法內部把這些 Promise 實例初始化出來。
function promiseFactory(fn) { return function() { return new Promise(fn); }; } const p1 = promiseFactory((resolve) => setTimeout(() => resolve(1), 1500)); const p2 = promiseFactory((resolve) => setTimeout(() => resolve(2), 2000)); const p3 = promiseFactory((resolve) => setTimeout(() => resolve(3), 1000)); console.time('hello world!'); Promise.all2([p1, p2, p3], 1).then((r) => { console.log(r) console.timeEnd('hello world!'); // 4500+ms打印結果 }); Promise.all2([p1, p2, p3], 2).then((r) => { console.log(r) console.timeEnd('hello world!'); // 2500+ms打印結果 }); Promise.all2([p1, p2, p3]).then((r) => { console.log(r) console.timeEnd('hello world!'); // 2000+ms打印結果 });
爲了能看出明顯的區別咱們把定時器的時間延長到秒,執行代碼後完美驗證。併發
至此結束。異步