Wie man viele Versprechen zurückgibt und auf sie alle wartet, bevor man andere Sachen macht

85

Ich habe eine Schleife, die eine Methode aufruft, die asynchron arbeitet. Diese Schleife kann die Methode viele Male aufrufen. Nach dieser Schleife habe ich eine weitere Schleife, die nur ausgeführt werden muss, wenn alle asynchronen Aufgaben erledigt sind.

Das zeigt also, was ich will:

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

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

Ich bin mit Versprechungen nicht sehr vertraut. Kann mir jemand helfen, dies zu erreichen?

So doSomeAsyncStuff()verhalte ich mich :

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

Vielleicht muss ich so etwas machen:

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

Aber ich bin mir der Syntax nicht sicher.

Ganbin
quelle
Haben Sie die Kontrolle über die asynchronen Aufrufe? Geben sie bereits Versprechen zurück oder können Sie sie dazu bringen, Versprechen zurückzugeben?
TJ Crowder
Was genau ist die Reihenfolge? Müssen Sie die anderen Funktionen aufrufen, nachdem alle vorherigen asynchronen Funktionen abgeschlossen sind? Oder müssen Sie nur eine Funktion aufrufen, nachdem alle Asynchronisierungen abgeschlossen sind?
Sosdoc
Im Moment gibt die erste Funktion keine Versprechen zurück. Das muss ich umsetzen. Ich möchte meine Nachricht bearbeiten, um einige Details des Workflows meiner Funktionen hinzuzufügen. Und ja, ich brauche, dass alle Sachen der ersten Schleife fertig sind, bevor ich anfange, die Sachen in der zweiten Schleife auszuführen.
Ganbin
1
Zu Ihrer Bearbeitung: "Vielleicht muss ich so etwas tun" Ja, sehr ähnlich, außer dass es sam Ende keine gibt Promise.
TJ Crowder

Antworten:

161

Sie können dafür Promise.all( spec , MDN ) verwenden: Es akzeptiert eine Reihe einzelner Versprechen und gibt Ihnen ein einzelnes Versprechen zurück, das aufgelöst wird, wenn alle von Ihnen gegebenen Versprechen gelöst werden, oder abgelehnt wird, wenn eines von ihnen abgelehnt wird.

Wenn Sie also doSomeAsyncStuffein Versprechen zurückgeben, dann:

    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 hat einen Artikel über Versprechungen hier . Ich beschreibe auch Proms ausführlich in Kapitel 8 meines Buches JavaScript: The New Toys , Links in meinem Profil, wenn Sie interessiert sind.

Hier ist ein Beispiel:

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

Beispielausgabe (aufgrund dessen kann das Math.random, was zuerst endet, variieren):

Lösung 3
2
1 lösen
Lösung 4
0 auflösen
Alles erledigt [0,1,2,3,4]
TJ Crowder
quelle
Ok, danke, ich versuche es jetzt und komme in wenigen Minuten mit Feedback.
Ganbin
12
Wow, vielen Dank, jetzt verstehe ich die Versprechen viel besser. Ich lese viel über Versprechen, aber bis wir sie in echtem Code verwenden müssen, verstehen wir nicht wirklich alle Mechanismen. Jetzt verstehe ich es besser und kann dank dir anfangen, coole Sachen zu schreiben.
Ganbin
1
Wenn Sie möchten, dass diese Aufgaben aus irgendeinem Grund ausgeführt werden (z. B. um den Fortschritt zu verspotten), können Sie Math.floor(Math.random() * 1000)zu(i * 1000)
OK, sicher,
@TJ jetzt, wie ich die Ergebnisdaten in die Ansicht rendern kann und dort kann ich die Schleife machen, um die Daten anzuzeigen
Ajit Singh
1
@ user1063287 - Sie können dies tun, wenn sich der Code in einem Kontext awaitbefindet, in dem dies zulässig ist. Im Moment können Sie nur awaiteine asyncFunktion verwenden. (Irgendwann können Sie es auch auf der obersten Ebene der Module verwenden.)
TJ Crowder
5

Eine wiederverwendbare Funktion funktioniert gut für dieses Muster:

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

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

  return Promise.all(promises);
}

OP-Beispiel:

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

Ein verwandtes Muster iteriert über ein Array und führt für jedes Element eine asynchrone Operation aus:

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

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

  return Promise.all(promises);
}

Beispiel:

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));
2Laden
quelle
1
Dies macht Code wirklich verständlicher und sauberer. Ich denke nicht, dass das aktuelle Beispiel (das offensichtlich an den OP-Code angepasst wurde) dies gerecht wird. Das ist ein ordentlicher Trick, danke!
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
quelle
1

Hier ist Code, den ich für mich selbst geschrieben habe, um die hier angegebenen Antworten zu verstehen. Ich habe Mungo-Abfragen in einer for-Schleife, also setze ich hier die asyncFunction, um ihren Platz einzunehmen. Hoffe es hilft jedem. Sie können dieses Skript im Knoten oder in einer von vielen Javascript-Laufzeiten ausführen.

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
quelle
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
quelle