Aquí está mi solución ES7 para copiar y pegar y contar con una característica completa Promise.all()
/ map()
alternativa, con un límite de concurrencia.
Similar a Promise.all()
, mantiene el orden de devolución, así como un respaldo para los valores de devolución no prometidos.
También incluí una comparación de las diferentes implementaciones, ya que ilustra algunos aspectos que algunas de las otras soluciones han pasado por alto.
Uso
const asyncFn = delay => new Promise(resolve => setTimeout(() => resolve(), delay));
const args = [30, 20, 15, 10];
await asyncPool(args, arg => asyncFn(arg), 4);
Implementación
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();
});
}
Comparación
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);
Conclusión
asyncPool()
debería ser la mejor solución, ya que permite que se inicien nuevas solicitudes tan pronto como finalice una anterior.
asyncBatch()
se incluye como una comparación, ya que su implementación es más simple de entender, pero debería tener un rendimiento más lento, ya que todas las solicitudes del mismo lote deben finalizar para iniciar el siguiente lote.
En este ejemplo artificial, la vainilla no limitada Promise.all()
es, por supuesto, la más rápida, mientras que las otras podrían tener un desempeño más deseable en un escenario de congestión del mundo real.
Actualizar
La biblioteca async-pool que otros ya han sugerido es probablemente una mejor alternativa a mi implementación, ya que funciona casi de manera idéntica y tiene una implementación más concisa con un uso inteligente de Promise.race (): https://github.com/rxaviers/ async-pool / blob / master / lib / es7.js
Espero que mi respuesta pueda tener un valor educativo.