Oto moje rozwiązanie ES7, które jest przyjazne dla kopiowania i wklejania i ma pełną Promise.all()
/ map()
alternatywną funkcję , z limitem współbieżności.
Podobnie Promise.all()
zachowuje kolejność zwrotu, a także rezerwę dla wartości zwrotów niezwiązanych z obietnicą.
Dołączyłem również porównanie różnych implementacji, ponieważ ilustruje to niektóre aspekty, które zostały pominięte w kilku innych rozwiązaniach.
Stosowanie
const asyncFn = delay => new Promise(resolve => setTimeout(() => resolve(), delay));
const args = [30, 20, 15, 10];
await asyncPool(args, arg => asyncFn(arg), 4);
Realizacja
async function asyncBatch(args, fn, limit = 8) {
args = [...args];
const outs = [];
while (args.length) {
const batch = args.splice(0, limit);
const out = await Promise.all(batch.map(fn));
outs.push(...out);
}
return outs;
}
async function asyncPool(args, fn, limit = 8) {
return new Promise((resolve) => {
const argQueue = [...args].reverse();
let count = 0;
const outs = [];
const pollNext = () => {
if (argQueue.length === 0 && count === 0) {
resolve(outs);
} else {
while (count < limit && argQueue.length) {
const index = args.length - argQueue.length;
const arg = argQueue.pop();
count += 1;
const out = fn(arg);
const processOut = (out, index) => {
outs[index] = out;
count -= 1;
pollNext();
};
if (typeof out === 'object' && out.then) {
out.then(out => processOut(out, index));
} else {
processOut(out, index);
}
}
}
};
pollNext();
});
}
Porównanie
const asyncFn = delay => new Promise(resolve => setTimeout(() => {
console.log(delay);
resolve(delay);
}, delay));
const args = [30, 20, 15, 10];
const out1 = await Promise.all(args.map(arg => asyncFn(arg)));
const out2 = await asyncPool(args, arg => asyncFn(arg), 2);
const out3 = await asyncBatch(args, arg => asyncFn(arg), 2);
console.log(out1, out2, out3);
Wniosek
asyncPool()
powinno być najlepszym rozwiązaniem, ponieważ umożliwia rozpoczęcie nowych żądań zaraz po zakończeniu poprzedniego.
asyncBatch()
jest uwzględniony jako porównanie, ponieważ jego implementacja jest prostsza do zrozumienia, ale powinna działać wolniej, ponieważ wszystkie żądania z tej samej partii muszą zostać zakończone, aby rozpocząć następną.
W tym wymyślonym przykładzie wanilia bez ograniczeń Promise.all()
jest oczywiście najszybsza, podczas gdy inne mogłyby działać bardziej pożądane w scenariuszu zatoru w świecie rzeczywistym.
Aktualizacja
Biblioteka async-pool, którą inni już zasugerowali, jest prawdopodobnie lepszą alternatywą dla mojej implementacji, ponieważ działa prawie identycznie i ma bardziej zwięzłą implementację dzięki sprytnemu wykorzystaniu Promise.race (): https://github.com/rxaviers/ async-pool / blob / master / lib / es7.js
Mam nadzieję, że moja odpowiedź może nadal mieć wartość edukacyjną.