Cómo devolver muchas promesas y esperarlas todas antes de hacer otras cosas

85

Tengo un bucle que llama a un método que hace cosas de forma asincrónica. Este bucle puede llamar al método muchas veces. Después de este bucle, tengo otro bucle que debe ejecutarse solo cuando se hacen todas las cosas asincrónicas.

Entonces esto ilustra lo que quiero:

for (i = 0; i < 5; i++) {
    doSomeAsyncStuff();    
}

for (i = 0; i < 5; i++) {
    doSomeStuffOnlyWhenTheAsyncStuffIsFinish();    
}

No estoy muy familiarizado con las promesas, entonces, ¿alguien podría ayudarme a lograrlo?

Así es como se doSomeAsyncStuff()comporta mi :

function doSomeAsyncStuff() {
    var editor = generateCKEditor();
    editor.on('instanceReady', function(evt) {
        doSomeStuff();
        // There should be the resolve() of the promises I think.
    })
}

Tal vez tenga que hacer algo como esto:

function doSomeAsyncStuff() {
    var editor = generateCKEditor();
    return new Promise(function(resolve,refuse) {
        editor.on('instanceReady', function(evt) {
            doSomeStuff();
            resolve(true);
        });
    });
}

Pero no estoy seguro de la sintaxis.

Ganbin
fuente
¿Tiene el control de las llamadas asincrónicas? ¿Ya devuelven las promesas o puede hacer que devuelvan las promesas?
TJ Crowder
¿Cuál es exactamente la secuencia? ¿Necesita llamar a las otras funciones después de que todas las anteriores asincrónicas estén terminadas? ¿O solo necesita llamar a una función después de que cada uno de los async haya finalizado?
Sosdoc
Por ahora, la primera función no devuelve promesas. Eso lo tengo que implementar. Quiero editar mi mensaje para agregar algunos detalles del flujo de trabajo de mis funciones. Y sí, necesito que todas las cosas del primer ciclo se terminen antes de comenzar a ejecutar las cosas en el segundo ciclo.
Ganbin
1
Re tu edición: "Tal vez tengo que hacer algo así" Sí, muy parecido a eso, excepto que no hay un sal final de Promise.
TJ Crowder

Respuestas:

161

Puede usar Promise.all( spec , MDN ) para eso: acepta un montón de promesas individuales y le devuelve una única promesa que se resuelve cuando se resuelven todas las que le dio, o se rechaza cuando se rechaza alguna de ellas.

Entonces, si hace doSomeAsyncStuffuna promesa de devolución, entonces:

    const promises = [];
//  ^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−− use `const` or `let`, not `var`
    
    for (let i = 0; i < 5; i++) {
//       ^^^−−−−−−−−−−−−−−−−−−−−−−−− added missing declaration
        promises.push(doSomeAsyncStuff());
    }
    
    Promise.all(promises)
        .then(() => {
            for (let i = 0; i < 5; i++) {
//               ^^^−−−−−−−−−−−−−−−− added missing declaration
                doSomeStuffOnlyWhenTheAsyncStuffIsFinish();    
            }
        })
        .catch((e) => {
            // handle errors here
        });

MDN tiene un artículo sobre promesas aquí . También cubro las promesas en detalle en el Capítulo 8 de mi libro JavaScript: The New Toys , enlaces en mi perfil si estás interesado.

He aquí un ejemplo:

 function doSomethingAsync(value) {
     return new Promise((resolve) => {
         setTimeout(() => {
             console.log("Resolving " + value);
             resolve(value);
         }, Math.floor(Math.random() * 1000));
     });
   }
   
   function test() {
       const promises = [];
       
       for (let i = 0; i < 5; ++i) {
           promises.push(doSomethingAsync(i));
       }
       
       Promise.all(promises)
           .then((results) => {
               console.log("All done", results);
           })
           .catch((e) => {
               // Handle errors here
           });
   }
   
   test();

Salida de muestra (debido a que Math.random, lo que termina primero puede variar):

Resolviendo 3
Resolviendo 2
Resolviendo 1
Resolviendo 4
Resolviendo 0
Todo listo [0,1,2,3,4]
TJ Crowder
fuente
Ok, gracias. Intento esto ahora y vengo con comentarios en unos minutos.
Ganbin
12
Vaya, muchas gracias, ahora entiendo mucho más las promesas. Leí mucho sobre las promesas, pero hasta que no tengamos que usarlas en código real, no entendemos todos los mecanismos. Ahora lo mejoro y puedo empezar a escribir cosas interesantes, gracias a ti.
Ganbin
1
Además, si desea que estas tareas se completen en orden por cualquier motivo (por ejemplo, simulando progreso), puede cambiar Math.floor(Math.random() * 1000)a(i * 1000)
OK, seguro,
@TJ ahora, ¿cómo puedo representar los datos de resultado en la vista y allí puedo hacer el ciclo para mostrar los datos?
Ajit Singh
1
@ user1063287: puede hacerlo si el código está en un contexto en el que awaitestá permitido. Por el momento, el único lugar que puede usar awaites dentro de una asyncfunción. (En algún momento, también podrá usarlo en el nivel superior de módulos).
TJ Crowder
5

Una función reutilizable funciona muy bien para este patrón:

function awaitAll(count, asyncFn) {
  const promises = [];

  for (i = 0; i < count; ++i) {
    promises.push(asyncFn());
  }

  return Promise.all(promises);
}

Ejemplo de OP:

awaitAll(5, doSomeAsyncStuff)
  .then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
  .catch(e => console.error(e));

Un patrón relacionado es iterar sobre una matriz y realizar una operación asíncrona en cada elemento:

function awaitAll(list, asyncFn) {
  const promises = [];

  list.forEach(x => {
    promises.push(asyncFn(x));
  });

  return Promise.all(promises);
}

Ejemplo:

const books = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }];

function doSomeAsyncStuffWith(book) {
  return Promise.resolve(book.name);
}

awaitAll(books, doSomeAsyncStuffWith)
  .then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
  .catch(e => console.error(e));
2 sapo
fuente
1
Esto realmente hace que el código sea más fácil de entender y más limpio. No creo que el ejemplo actual (que obviamente se adaptó al código de OP) haga esta justicia. ¡Este es un buen truco, gracias!
Shaun Vermaak
2
const doSomeAsyncStuff = async (funcs) => {
  const allPromises = funcs.map(func => func());
  return await Promise.all(allPromises);
}

doSomeAsyncStuff([
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
]);
JoeTidee
fuente
1

Aquí hay un código que escribí para mí mismo para comprender las respuestas que se indican aquí. Tengo consultas de mangosta en un bucle for, así que puse aquí el asyncFunctionpara que ocupe su lugar. Espero que ayude a alguien. Puede ejecutar este script en el nodo o en cualquiera de los muchos tiempos de ejecución de Javascript.

let asyncFunction = function(value, callback)
{
        setTimeout(function(){console.log(value); callback();}, 1000);
}



// a sample function run without promises

asyncFunction(10,
    function()
    {
        console.log("I'm back 10");
    }
);


//here we use promises

let promisesArray = [];

let p = new Promise(function(resolve)
{
    asyncFunction(20,
        function()
        {
            console.log("I'm back 20");
            resolve(20);
        }
    );
});

promisesArray.push(p);


for(let i = 30; i < 80; i += 10)
{
    let p = new Promise(function(resolve)
    {
        asyncFunction(i,
            function()
            {
                console.log("I'm back " + i);
                resolve(i);
            }
        );
    });
    promisesArray.push(p);
}


// We use Promise.all to execute code after all promises are done.

Promise.all(promisesArray).then(
    function()
    {
        console.log("all promises resolved!");
    }
)
Mina Michael
fuente
0

/*** Worst way ***/
for(i=0;i<10000;i++){
  let data = await axios.get(
    "https://yourwebsite.com/get_my_data/"
  )
  //do the statements and operations
  //that are dependant on data
}

//Your final statements and operations
//That will be performed when the loop ends

//=> this approach will perform very slow as all the api call
// will happen in series


/*** One of the Best way ***/

const yourAsyncFunction = async (anyParams) => {
  let data = await axios.get(
    "https://yourwebsite.com/get_my_data/"
  )
  //all you statements and operations here
  //that are dependant on data
}
var promises = []
for(i=0;i<10000;i++){
  promises.push(yourAsyncFunction(i))
}
await Promise.all(promises)
//Your final statement / operations
//that will run once the loop ends

//=> this approach will perform very fast as all the api call
// will happen in parallal

Sourav Purkait
fuente