Come restituire molte promesse e aspettarle tutte prima di fare altre cose


91

Ho un ciclo che chiama un metodo che fa cose in modo asincrono. Questo ciclo può chiamare il metodo molte volte. Dopo questo ciclo, ho un altro ciclo che deve essere eseguito solo quando tutte le cose asincrone sono state completate.

Quindi questo illustra quello che voglio:

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

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

Non ho molta familiarità con le promesse, quindi qualcuno potrebbe aiutarmi a raggiungere questo obiettivo?

Ecco come doSomeAsyncStuff()si comporta il mio :

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

Forse devo fare qualcosa del genere:

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

Ma non sono sicuro della sintassi.


Hai il controllo delle chiamate asincrone? Restituiscono già le promesse o puoi fargliela restituire?
TJ Crowder

Qual è esattamente la sequenza? Hai bisogno di chiamare le altre funzioni dopo che tutte le precedenti asincrone sono terminate? O hai solo bisogno di chiamare una funzione dopo che ogni asincrono è terminato?
Sosdoc

Per ora la prima funzione non restituisce promesse. Che devo implementare. Voglio modificare il mio messaggio per aggiungere alcuni dettagli del flusso di lavoro delle mie funzioni. E sì, ho bisogno che tutte le cose del primo ciclo siano finite prima di iniziare a eseguire le cose nel secondo ciclo.
Ganbin

1
Re la tua modifica: "Forse devo fare qualcosa del genere" Sì, proprio così, tranne che non c'è salla fine Promise.
TJ Crowder

Risposte:


169

Puoi usare Promise.all( spec , MDN ) per questo: accetta un mucchio di singole promesse e ti restituisce una singola promessa che viene risolta quando tutte quelle che hai dato vengono risolte o rifiutate quando una di esse viene rifiutata.

Quindi, se doSomeAsyncStuffrestituisci una promessa, allora:

    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 ha un articolo sulle promesse qui . Tratto anche le promesse in dettaglio nel capitolo 8 del mio libro JavaScript: The New Toys , link nel mio profilo se sei interessato.

Ecco un esempio:

 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();

Output di esempio (a causa di Math.randomciò che finisce per primo può variare):

Risoluzione 3
Risoluzione 2
Risoluzione 1
Risoluzione 4
Risoluzione di 0
Tutto fatto [0,1,2,3,4]

Ok grazie, lo provo ora e ricevo un feedback in pochi minuti.
Ganbin

12
Wow, grazie mille, ora capisco molto di più le promesse, ho letto molto sulle promesse, ma fino a quando non avremo bisogno di usarle in codice reale, non capiamo davvero tutti i meccanismi. Ora riesco a migliorare e posso iniziare a scrivere cose interessanti, grazie a te.
Ganbin

1
Inoltre, se desideri completare queste attività in ordine per qualsiasi motivo (ad esempio per deridere i progressi), puoi passare Math.floor(Math.random() * 1000)a(i * 1000)
OK, certo

@TJ ora come posso visualizzare i dati del risultato nella vista e lì posso fare il ciclo per mostrare i dati
Ajit Singh

1
@ user1063287 - Puoi farlo se il codice si trova in un contesto in cui awaitè consentito. Al momento, l'unico posto che puoi usare awaitè all'interno di una asyncfunzione. (Ad un certo punto sarai anche in grado di usarlo al livello più alto dei moduli.)
TJ Crowder

6

Una funzione riutilizzabile funziona bene per questo modello:

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

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

  return Promise.all(promises);
}

Esempio OP:

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

Un modello correlato, sta iterando su un array ed eseguendo un'operazione asincrona su ogni elemento:

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

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

  return Promise.all(promises);
}

Esempio:

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));

1
Questo rende davvero il codice più facile da capire e più pulito. Non credo che l'esempio attuale (che è stato ovviamente adattato al codice di OP) renda giustizia a questo. Questo è un bel trucco, grazie!
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)),
]);

2

Ecco il codice che ho scritto per me stesso per capire le risposte qui indicate. Ho delle query sulle manguste in un ciclo for, quindi metto qui il asyncFunctionper prendere il suo posto. Spero che aiuti chiunque. È possibile eseguire questo script nel nodo o in uno qualsiasi dei tanti runtime 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!");
    }
)

1

/*** 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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.