¿Cómo devuelvo los resultados acumulados de múltiples llamadas a función asíncrona (paralelas) en un bucle?

Tengo una función foo que hace llamadas asincrónicas múltiples (paralelas) en un bucle. Necesito de alguna manera esperar hasta que estén disponibles los resultados de todas las llamadas. ¿Cómo puedo devolver los resultados completos de foo , o de lo contrario desencadenar algún procesamiento después de que todos los datos estén disponibles?

Traté de agregar cada resultado a una matriz, pero luego la matriz no se llena hasta después del punto donde necesito usarla.

 function foo() { var results = []; for (var i = 0; i < 10; i++) { someAsyncFunction({someParam:i}, function callback(data) { results.push(data); }); } return results; } var result = foo(); // It always ends up being an empty array at this point. 

Nota: esta pregunta es deliberadamente genérica según las líneas del mensaje genérico “¿Cómo devuelvo la respuesta de una llamada asíncrona?” pregunta Esa pregunta tiene algunas respuestas excelentes, pero no cubre llamadas asincrónicas múltiples. Hay algunas otras preguntas que mencionan llamadas múltiples, pero no pude encontrar ninguna basada en bucles, y algunas solo tenían respuestas jQuery, etc. Espero aquí algunas técnicas genéricas que no dependen de una biblioteca en particular.

Usa promesas Precisamente, Promise.all fue diseñado para esto.

Toma una matriz (o iterable) de promesas y devuelve una nueva promesa que se resuelve cuando se han resuelto todas las promesas de la matriz. De lo contrario, rechaza cuando cualquier promesa de la matriz rechaza.

 function someAsyncFunction(data, resolve, reject) { setTimeout(function() { if(Math.random() < .05) { // Suppose something failed reject('Error while processing ' + data.someParam); } else { // Suppose the current async work completed succesfully resolve(data.someParam); } }, Math.random() * 1000); } function foo() { // Create an array of promises var promises = []; for (var i = 0; i < 10; i++) { // Fill the array with promises which initiate some async work promises.push(new Promise(function(resolve, reject) { someAsyncFunction({someParam:i}, resolve, reject); })); } // Return a Promise.all promise of the array return Promise.all(promises); } var result = foo().then(function(results) { console.log('All async calls completed successfully:'); console.log(' --> ', JSON.stringify(results)); }, function(reason) { console.log('Some async call failed:'); console.log(' --> ', reason); }); 

Una forma sencilla de hacerlo sería activar una callback una vez que todas las respuestas estén en la matriz:

 function foo(cb) { var results = []; for (var i = 0; i < 10; i++) { someAsyncFunction({someParam:i}, function callback(data) { results.push(data); if(results.length===10){ cb(results); } }); } } foo(function(resultArr){ // do whatever with array of results }); 

La única diferencia con respecto al enfoque Promise.all es que el orden de los resultados no está garantizado; pero eso es fácilmente alcanzable con algunas adiciones.

Hace mucho tiempo, he respondido una pregunta muy similar aquí: coordinación de ejecución paralela en node.js.

Sin embargo, los tiempos han cambiado. Desde entonces, ha aparecido una biblioteca realmente buena y el patrón de diseño de la promesa ha sido completamente explorado e incluso estandarizado en el lenguaje. Si desea ver cómo se puede hacer con código sin formato, haga clic en el enlace de arriba. Si solo quieres leer el código …

async.js

La biblioteca async.js básicamente implementó el código en el enlace de arriba. Con la función asincrónica, el código que escribirías se vería así:

 var listOfAsyncFunctions = []; for (var i = 0; i < 10; i++) { (function(n){ // Construct an array of async functions with the expected // function signature (one argument that is the callback). listOfAsyncFunctions.push(function(callback){ // Note: async expects the first argument to callback to be an error someAsyncFunction({someParam:n}, function (data) { callback(null,data); }); }) })(i); // IIFE to break the closure } // Note that at this point you haven't called the async functions. // Pass the array to async.js and let it call them. async.parallel(listOfAsyncFunctions,function (err,result) { console.log(result); // result will be the same order as listOfAsyncFunctions }); 

Sin embargo, los autores de async.js han hecho más que eso. Async también tiene operaciones funcionales tipo array: cada una, mapa, filtro, reducir. Hace que el procesamiento asincrónico de matrices sea simple y facilita la comprensión del código:

 var listOfParams = []; for (var i = 0; i < 10; i++) { // Construct an array of params: listOfParams.push({someParam:i}); } async.map(listOfParams,someAsyncFunction,function (err,result) { console.log(result); }); 

Otra cosa que async le da es diferentes algoritmos sobre cómo procesar las tareas asincrónicas. Digamos, por ejemplo, que quiere robar un sitio web, pero no quiere que bloquee su dirección IP para enviar correo basura a su servidor. Puede usar async.series() lugar de parallel para procesar las tareas de a una por vez:

 // Set-up listOfAsyncFunctions as above async.series(listOfAsyncFunctions,function (err,result) { console.log(result); // result will be the same order as listOfAsyncFunctions }); 

O si desea procesar 3 tareas a la vez:

 async. parallelLimit(listOfAsyncFunctions, 3, function (err,result) { console.log(result); // result will be the same order as listOfAsyncFunctions }); 

Promise.all ()

El método Promise.all() funciona de manera similar a async.parallel() solo que funciona con promesas. Construyes una serie de promesas y luego las pasas a Promise.all() :

 var listOfPromises = []; for (var i = 0; i < 10; i++) { // Construct an array of promises listOfPromises.push(somePromiseFunction({someParam:i})); } Promise.all(listOfPromises).then(function(result){ console.log(result); });