Aqui está minha solução ES7 para copiar e colar amigável e recurso completo Promise.all()
/ map()
alternativo, com um limite de simultaneidade.
Semelhante a Promise.all()
ele mantém a ordem de devolução, bem como um fallback para valores de retorno não prometidos.
Também incluí uma comparação das diferentes implementações, pois ela ilustra alguns aspectos que algumas das outras soluções deixaram de lado.
Uso
const asyncFn = delay => new Promise(resolve => setTimeout(() => resolve(), delay));
const args = [30, 20, 15, 10];
await asyncPool(args, arg => asyncFn(arg), 4);
Implementação
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();
});
}
Comparação
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);
Conclusão
asyncPool()
deve ser a melhor solução, pois permite que novas solicitações sejam iniciadas assim que as anteriores forem concluídas.
asyncBatch()
está incluído como uma comparação porque sua implementação é mais simples de entender, mas deve ter um desempenho mais lento, pois todas as solicitações no mesmo lote precisam terminar para iniciar o próximo lote.
Neste exemplo inventado, a baunilha não limitada Promise.all()
é obviamente o mais rápido, enquanto os outros poderiam ter um desempenho mais desejável em um cenário de congestionamento do mundo real.
Atualizar
A biblioteca de pool assíncrono que outros já sugeriram é provavelmente uma alternativa melhor para minha implementação, pois funciona quase de forma idêntica e tem uma implementação mais concisa com um uso inteligente de Promise.race (): https://github.com/rxaviers/ async-pool / blob / master / lib / es7.js
Espero que minha resposta ainda possa servir a um valor educacional.