Blockiert die Verwendung von Warten in einer Schleife in JavaScript die Schleife?

74

Nehmen Sie die folgende Schleife:

for(var i=0; i<100; ++i){
    let result = await some_slow_async_function();
    do_something_with_result();
}
  1. Hat awaitblockieren die Schleife? Oder wird das iwährend des awaitIng weiter erhöht ?

  2. Ist die Reihenfolge der do_something_with_result()garantierten Reihenfolge in Bezug auf i? Oder hängt es davon ab, wie schnell die awaited-Funktion für jede ist i?

Smorgs
quelle
7
Hast du es tatsächlich ausprobiert?
Bergi
3
Ja, ich erhalte ein sequentielles Ergebnis. Ich war mir nicht sicher, ob es ein Zufall war (die asynchrone Funktion ist tatsächlich schnell). Ich war mir nicht sicher, ob ich alle asynchronen Funktionsaufrufe in ein Array verschieben und dann ein einzelnes ausführen await Promise.all(arr)sollte oder ob dieses Formular korrekt war und etwas anderes die gewünschte Asynchronität behinderte. Wenn ich awaitfür alle eine Single mache , müsste ich mich darauf einlassen, um Promise.mapmit jeder fertig zu werden. Ich .thenfrage mich, ob es in dieser Situation besser ist als asynchron / warten.
Smorgs
JS ist deterministisch. Entweder ist die Funktion asynchron oder nicht, es kommt nie darauf an, wie schnell etwas ausgeführt wird. In Bezug Promise.alldarauf macht das etwas anderes - ob es noch richtig (oder noch wünschenswerter) ist, hängt von Ihren Anforderungen ab.
Bergi
Die Ausführungszeit einer Funktion ist nicht deterministisch, wenn eine asyncOperation ausgeführt wird, die eine externe Ressource umfasst, z. B. Datenbank, Datei-E / A.
Smorgs
2
Ja, aber solange es asynchron ist, ruft es seine Rückrufe niemals sofort auf und wird immer zuerst ausgeführt, bis der Synchronisierungscode vollständig ist.
Bergi

Antworten:

75
  1. Hat awaitblockieren die Schleife? Oder wird das iwährend des awaitIng weiter erhöht ?

"Block" ist nicht das richtige Wort, aber ja, ich werde nicht weiter erhöht, während ich warte. Stattdessen springt die Ausführung zurück zu dem Ort, an dem die asyncFunktion aufgerufen wurde, und gibt ein Versprechen als Rückgabewert aus. Der Rest des nach dem Funktionsaufruf folgenden Codes wird fortgesetzt, bis der Codestapel geleert wurde. Wenn das Warten beendet ist, wird der Status der Funktion wiederhergestellt und die Ausführung innerhalb dieser Funktion fortgesetzt. Immer wenn diese Funktion zurückkehrt (abgeschlossen wird), wird das entsprechende Versprechen - das zuvor zurückgegeben wurde - aufgelöst.

  1. Ist die Reihenfolge der do_something_with_result()garantierten Reihenfolge in Bezug auf i? Oder hängt es davon ab, wie schnell die awaited-Funktion für jede ist i?

Die Bestellung ist garantiert. Der Code, der auf folgt, awaitwird garantiert erst ausgeführt, nachdem der Aufrufstapel geleert wurde, dh zumindest bei oder nach der Ausführung der nächsten Mikrotask.

Sehen Sie, wie die Ausgabe in diesem Snippet ist. Beachten Sie insbesondere, wo "nach dem Aufruf von test" steht:

async function test() {
    for (let i = 0; i < 2; i++) {
        console.log('Before await for ', i);
        let result = await Promise.resolve(i);
        console.log('After await. Value is ', result);
    }
}

test().then(_ => console.log('After test() resolved'));

console.log('After calling test');

Trincot
quelle
Die Stack-Erklärung erinnert mich daran, dass sie async/awaitbis vor kurzem mit Generatoren / implementiert wurde yield. Wenn man es so betrachtet, wird alles klarer. Ich werde diese Antwort akzeptieren.
Smorgs
1
@smorgs Die Stack-Erklärung hat eigentlich mehr mit Standard-Versprechen zu tun thenals mit Generatoren. Es werden nur "Zurückspringen an den Ort, an dem es aufgerufen wurde" und "Zustand der Funktion wird wiederhergestellt" angezeigt yield.
Bergi
Verstanden; Ich finde das Stapelmodell einfach zu sehen, .thenaber nicht so await, also denke yieldich daran , dass es meine Verwirrung löst.
Smorgs
13

Wie @realbart sagt, blockiert es die Schleife, wodurch die Aufrufe sequentiell werden.

Wenn Sie eine Menge erwartbarer Vorgänge auslösen und dann alle zusammen ausführen möchten, können Sie Folgendes tun:

const promisesToAwait = [];
for (let i = 0; i < 100; i++) {
  promisesToAwait.push(fetchDataForId(i));
}
const responses = await Promise.all(promisesToAwait);
Kris Selbekk
quelle
3
Diese Antwort beantwortet die Frage nicht wirklich
Liam
Das macht keinen Sinn. Ihr promisesToAwaitArray wird niemals Versprechen enthalten.
Bergi
1
Die Schleife wird nicht blockiert, aber auch nicht wie erwartet ausgeführt.
Allan Felipe Murara
10

Sie können async / await in einer "FOR LOOP" wie folgt testen:

(async  () => {
        for (let i = 0; i < 100; i++) {
                await delay();
                console.log(i);
        }
})();

function delay() {
        return new Promise((resolve, reject) => {
                setTimeout(resolve, 100);
        });
}

mzalazar
quelle
1

Anzeige 1. Ja. Nein.

Anzeige 2. Ja. Nein.

Beweis:

Kamil Kiełczewski
quelle
0

Keine Ereignisschleife ist nicht blockiert, siehe Beispiel unten

function sayHelloAfterSomeTime (ms) {
  return new Promise((resolve, reject) => {
    if (typeof ms !== 'number') return reject('ms must be a number')
    setTimeout(() => { 
      console.log('Hello after '+ ms / 1000 + ' second(s)')
      resolve()  
    }, ms)
  })
}

async function awaitGo (ms) {
   await sayHelloAfterSomeTime(ms).catch(e => console.log(e))
   console.log('after awaiting for saying Hello, i can do another things  ...')
}

function notAwaitGo (ms) {
	sayHelloAfterSomeTime(ms).catch(e => console.log(e))
    console.log('i dont wait for saying Hello ...')
}

awaitGo(1000)
notAwaitGo(1000)
console.log('coucou i am event loop and i am not blocked ...')

KBH
quelle
0

Hier ist meine Testlösung zu dieser interessanten Frage:

import crypto from "crypto";

function diyCrypto() {
    return new Promise((resolve, reject) => {
        crypto.pbkdf2('secret', 'salt', 2000000, 64, 'sha512', (err, res) => {
            if (err) {
                reject(err)
                return 
            }
            resolve(res.toString("base64"))
        })
    })
}

setTimeout(async () => {
    console.log("before await...")
    const a = await diyCrypto();
    console.log("after await...", a)
}, 0);

setInterval(() => {
    console.log("test....")
}, 200);

Innerhalb des Rückrufs von setTimeout awaitblockiert die Ausführung. Aber das setIntervalläuft weiter, so dass die Ereignisschleife wie gewohnt läuft.

Chris Bao
quelle
-1

asyncFunktionen geben ein Versprechen zurück, bei dem es sich um ein Objekt handelt, das schließlich in einen Wert "aufgelöst" oder mit einem Fehler "abgelehnt" wird. Das awaitSchlüsselwort bedeutet warten, bis dieser Wert (oder Fehler) abgeschlossen ist.

Aus Sicht der laufenden Funktion wird das Warten auf das Ergebnis der langsamen asynchronen Funktion blockiert . Die Javascript-Engine hingegen erkennt, dass diese Funktion blockiert ist und auf das Ergebnis wartet. Daher überprüft sie die Ereignisschleife (z. B. neue Mausklicks oder Verbindungsanfragen usw.), um festzustellen, ob andere Dinge vorhanden sind Es kann so lange bearbeitet werden, bis die Ergebnisse zurückgegeben werden.

Beachten Sie jedoch, dass die Javascript-Engine nicht viele Ressourcen hat, um andere Aufgaben zu erledigen, wenn die langsame Async-Funktion langsam ist, weil sie viele Dinge in Ihrem Javascript-Code berechnet (und wenn Sie andere Dinge tun, würde dies wahrscheinlich die langsame Async-Funktion bewirken noch langsamer). Der Vorteil von Async-Funktionen liegt in E / A-intensiven Vorgängen wie dem Abfragen einer Datenbank oder dem Übertragen einer großen Datei, bei der die Javascript-Engine wirklich auf etwas anderes wartet (z. B. Datenbank, Dateisystem usw.).

Die folgenden zwei Codebits sind funktional äquivalent:

let result = await some_slow_async_function();

und

let promise = some_slow_async_function(); // start the slow async function
// you could do other stuff here while the slow async function is running
let result = await promise; // wait for the final value from the slow async function

Im zweiten Beispiel oben wird die langsame asynchrone Funktion ohne das awaitSchlüsselwort aufgerufen , sodass die Ausführung der Funktion gestartet und ein Versprechen zurückgegeben wird. Dann können Sie andere Dinge tun (wenn Sie andere Dinge zu tun haben). Dann wird das awaitSchlüsselwort verwendet, um zu blockieren, bis das Versprechen tatsächlich "aufgelöst" wird. Aus der Sicht der forSchleife läuft sie also synchron.

Damit:

  1. Ja, das awaitSchlüsselwort hat den Effekt, dass die laufende Funktion blockiert wird , bis die asynchrone Funktion entweder mit einem Wert "aufgelöst" oder mit einem Fehler "zurückgewiesen" wird, aber die Javascript-Engine nicht blockiert, die andere Dinge tun kann, wenn sie andere hat Dinge zu tun, während Sie warten

  2. Ja, die Ausführung der Schleife erfolgt sequentiell

Unter http://javascript.info/async gibt es ein großartiges Tutorial dazu .

Casey
quelle
"sieht, dass diese Funktion blockiert ist und auf das Ergebnis wartet, so dass die Ereignisschleife überprüft wird" : Nein, dies ist eine falsche Darstellung dessen, was passiert. Die Funktion kehrt tatsächlich zurück, wenn eine awaitauftritt, und die Codeausführung wird nach dem Funktionsaufruf fortgesetzt.
Trincot
@trincot Die Nuance ist speziell, welche Codeausführung nach dem Funktionsaufruf fortgesetzt wird. Ist es 1) der Code in der asynchronen Funktion, der fortgesetzt wird, oder 2) der Code in der aufrufenden Funktion nach dem Warten oder 3) andere Aktivitäten, die in die Ereignisschleife eingereiht sind. Aus der Perspektive der aufrufenden Funktion scheint es blockiert zu sein (Warten auf die Lösung des Versprechens), aus der Perspektive der asynchronen Funktion, die normal ausgeführt wird (aber möglicherweise auf externe E / A oder etwas wartet), und aus der Perspektive von Der Knoten überprüft die Ereignisschleife, um festzustellen, ob andere Dinge zu tun sind.
Casey
@trincot das ist auch der Grund, warum ich sagte "hat den 'Effekt', die laufende Funktion zu blockieren". Es ist nicht wirklich blockiert, und Node erledigt andere Aufgaben aus der Ereignisschleife (und arbeitet sich weiterhin durch die Ausführung der asynchronen Funktion), sondern aus der Perspektive der aufrufenden Funktion (nicht der aufgerufenen asynchronen Funktion). es "scheint" blockiert zu sein.
Casey
Die Erwähnung, dass der Knoten "die Ereignisschleife überprüft", ist irreführend. Knoten wird nicht die Ereignisschleife überprüfen , wenn awaitfestgestellt wird. Die Funktion kehrt zurück und die Codeausführung wird wie nach jedem Funktionsaufruf fortgesetzt . Die Ereignisschleife spielt nur dann eine Rolle, wenn der Aufrufstapel leer ist, was nicht der Fall ist, wenn er awaitauftritt. Übrigens: Bei dieser Frage geht es nicht speziell um Node, sondern um JavaScript im Allgemeinen.
Trincot
Sie haben auch geschrieben, "damit die Ereignisschleife überprüft wird (dh neue Mausklicks oder Verbindungsanfragen usw.)" . Das ist nicht wahr. (1) Die Codeausführung wird ohne Berücksichtigung der Ereignisschleife fortgesetzt , und (2) selbst wenn Ereignisse wie Mausklicks auftreten, haben sie keinen Vorrang vor Versprechenjobs, die sich in einer anderen Warteschlange befinden ("Promise Job Queue"). Eine Versprechungsauflösung (die den Funktionskontext am wiederherstellen würde await) hat Vorrang vor Mausklicks und anderen agentengesteuerten Ereignissen.
Trincot
-3

Blockiert das Warten die Schleife? Oder wird das iwährend des Wartens weiter erhöht?

Nein, warten wird die Schleife nicht blockieren. Ja, wird iwährend der Schleife weiter erhöht.

Ist die Reihenfolge von do_something_with_result () in Bezug auf garantiert sequentiell i? Oder hängt es davon ab, wie schnell die erwartete Funktion für jede ist i?

Die Reihenfolge do_something_with_result()wird nacheinander garantiert, jedoch nicht in Bezug aufi . Dies hängt davon ab, wie schnell die erwartete Funktion ausgeführt wird.

Alle Aufrufe an some_slow_async_function()werden gestapelt, dh wenn dies a do_something_with_result()war , wird angezeigt, consolewie oft die Schleife ausgeführt wird. Danach werden nacheinander alle erwarteten Aufrufe ausgeführt.

Zum besseren Verständnis können Sie das folgende Code-Snippet ausführen:

async function someFunction(){
for (let i=0;i<5;i++){
 await callAPI();
 console.log('After', i, 'th API call');
}
console.log("All API got executed");
}

function callAPI(){
setTimeout(()=>{
console.log("I was called at: "+new Date().getTime())}, 1000);
}

someFunction();

Man kann deutlich sehen, wie die Zeile console.log('After', i, 'th API call');zuerst für die gesamte Strecke der for-Schleife gedruckt wird und dann am Ende, wenn der gesamte Code ausgeführt wird, Ergebnisse erhalten callAPI().

Wenn also Leitungen nach dem Warten vom Ergebnis der Warte-Anrufe abhängen, funktionieren sie nicht wie erwartet.

Zusammenfassend lässt sich sagen, dass awaitin for-loopkeine erfolgreiche Operation für das Ergebnis von Warteanrufen gewährleistet ist , deren Abschluss einige Zeit in Anspruch nehmen kann.

Wenn man im Knoten eine neo-asyncBibliothek mit verwendet waterfall, kann man dies erreichen.

DeeKay
quelle
3
Dies liegt daran, dass Ihre callAPI-Funktion kein Versprechen zurückgibt. Kehren Sie also await callAPI()sofort zurück. Wenn Sie Ihre ändern callAPI(), um ein Versprechen zurückzugeben, wird es funktionieren.
Henry Liu