Wann ist JavaScript synchron?

202

Ich hatte den Eindruck, dass JavaScript immer asynchron war. Ich habe jedoch gelernt, dass es Situationen gibt, in denen dies nicht der Fall ist (dh DOM-Manipulationen). Gibt es irgendwo eine gute Referenz darüber, wann es synchron und wann es asynchron sein wird? Beeinflusst jQuery dies überhaupt?

Brian
quelle
14
Immer mit Ausnahme von Ajax.
Defau1t
Akzeptierte Antworten sind falsch und irreführend. Bitte überprüfen Sie sie.
Suraj Jain
2
War auch nützlich, youtube.com/watch?v=8aGhZQkoFbQ zu beobachten , um die Ereignisschleife zu verstehen und wie der Stack, die Web-APIs und die Task-Warteschlange in Bezug auf Synchronisierung und
Asynchronisierung funktionieren
1
@ defau1t Ist das nicht falsch? JavaScript ist immer synchron. Wenn der Ajax-Aufruf beendet ist, landet der Rückruf in der Warteschlange. Wie ist es eine Ausnahme von der Synchronität des Java-Skripts?
Suraj Jain

Antworten:

281

JavaScript ist immer synchron und Single-Threaded. Wenn Sie einen JavaScript-Codeblock auf einer Seite ausführen, wird derzeit kein anderes JavaScript auf dieser Seite ausgeführt.

JavaScript ist nur in dem Sinne asynchron, dass es beispielsweise Ajax-Aufrufe ausführen kann. Der Ajax-Aufruf wird nicht mehr ausgeführt, und anderer Code kann ausgeführt werden, bis der Aufruf (erfolgreich oder anderweitig) zurückgegeben wird. Zu diesem Zeitpunkt wird der Rückruf synchron ausgeführt. Zu diesem Zeitpunkt wird kein anderer Code ausgeführt. Es wird kein anderer Code unterbrochen, der gerade ausgeführt wird.

JavaScript-Timer arbeiten mit derselben Art von Rückruf.

JavaScript als asynchron zu beschreiben, ist möglicherweise irreführend. Es ist genauer zu sagen, dass JavaScript synchron und Single-Threaded mit verschiedenen Rückrufmechanismen ist.

jQuery hat eine Option für Ajax-Aufrufe, um sie synchron zu machen (mit der async: falseOption). Anfänger könnten versucht sein, dies falsch zu verwenden, da es ein traditionelleres Programmiermodell ermöglicht, an das man vielleicht eher gewöhnt ist. Der Grund dafür ist, dass diese Option das gesamte JavaScript auf der Seite blockiert, bis es abgeschlossen ist, einschließlich aller Ereignishandler und Timer.

Cletus
quelle
31
Entschuldigung, ich habe diese Aussage "Der Code wird nicht mehr ausgeführt, bis der Aufruf zurückkehrt (erfolgreich oder fehlerhaft)" nicht ganz verstanden. Könnten Sie näher darauf eingehen? Wie kann diese Aussage wahr sein, wenn Sie auch sagen "Es wird keinen anderen Code unterbrechen, der ausgeführt wird"; Sprechen Sie nur in der ersten Anweisung über Rückrufcode? Bitte erleuchte mich.
krishna
2
Nettuts hat ein Tutorial, das ziemlich gut darin ist, die Grundlagen von Async hier zu erklären: net.tutsplus.com/tutorials/javascript-ajax/…
RobW
26
@cletus Die Anweisung "Der Code wird nicht mehr ausgeführt, bis der Aufruf zurückgegeben wird" muss korrigiert werden, da die Ausführung nicht beendet wird. Die Codeausführung kann fortgesetzt werden. Andernfalls würde dies bedeuten, dass der Anruf synchron ist.
HS.
1
Ich habe diese Aussage auch nicht verstanden.
Towry
12
Diese Antwort ist unglaublich irreführend und verwirrend. Bitte lesen Sie stattdessen die Antwort von CMS oder Faraz Ahmad.
Iono
214

JavaScript ist Single-Threaded und verfügt über ein synchrones Ausführungsmodell. Single Threaded bedeutet, dass jeweils ein Befehl ausgeführt wird. Synchron bedeutet, dass jeweils eine Codezeile ausgeführt wird, damit der Code angezeigt wird. In JavaScript passiert also jeweils eine Sache.

Ausführungskontext

Die JavaScript-Engine interagiert mit anderen Engines im Browser. Im JavaScript-Ausführungsstapel befindet sich unten ein globaler Kontext. Wenn wir dann Funktionen aufrufen, erstellt die JavaScript-Engine neue Ausführungskontexte für die jeweiligen Funktionen. Wenn die aufgerufene Funktion beendet wird, wird ihr Ausführungskontext aus dem Stapel entfernt, und dann wird der nächste Ausführungskontext angezeigt und so weiter ...

Beispielsweise

function abc()
{
   console.log('abc');
}


function xyz()
{
   abc()
   console.log('xyz');
}
var one = 1;
xyz();

Im obigen Code wird ein globaler Ausführungskontext erstellt und in diesem Kontext var onegespeichert. Sein Wert ist 1 ... Wenn der Aufruf von xyz () aufgerufen wird, wird ein neuer Ausführungskontext erstellt und wenn wir eine Variable definiert haben In der xyz-Funktion würden diese Variablen im Ausführungskontext von xyz () gespeichert. In der xyz-Funktion rufen wir abc () auf und dann wird der Ausführungskontext abc () erstellt und auf den Ausführungsstapel gelegt ... Wenn abc () fertig ist, wird der Kontext vom Stapel entfernt, und der Kontext xyz () wird aus dem Stapel entfernt Stapel und dann wird der globale Kontext gepoppt ...

Nun zu asynchronen Rückrufen; asynchron bedeutet mehr als eine gleichzeitig.

Genau wie beim Ausführungsstapel gibt es die Ereigniswarteschlange . Wenn wir über ein Ereignis in der JavaScript-Engine benachrichtigt werden möchten, können wir dieses Ereignis abhören, und dieses Ereignis wird in die Warteschlange gestellt. Zum Beispiel ein Ajax-Anforderungsereignis oder ein HTTP-Anforderungsereignis.

Wenn der Ausführungsstapel leer ist, wie im obigen Codebeispiel gezeigt, überprüft die JavaScript-Engine regelmäßig die Ereigniswarteschlange und prüft, ob ein Ereignis gemeldet werden muss. In der Warteschlange gab es beispielsweise zwei Ereignisse, eine Ajax-Anforderung und eine HTTP-Anforderung. Es wird auch geprüft, ob es eine Funktion gibt, die für diesen Ereignisauslöser ausgeführt werden muss ... Die JavaScript-Engine wird also über das Ereignis benachrichtigt und kennt die entsprechende Funktion, die für dieses Ereignis ausgeführt werden soll. Die JavaScript-Engine ruft also die auf Handlerfunktion, im Beispielfall, z. B. AjaxHandler (), und wie immer, wenn eine Funktion aufgerufen wird, wird ihr Ausführungskontext in den Ausführungskontext gestellt, und jetzt wird die Funktionsausführung beendet und die Ereignis-Ajax-Anforderung wird ebenfalls aus der Ereigniswarteschlange entfernt ... Wenn AjaxHandler () fertig ist, ist der Ausführungsstapel leer, sodass die Engine die Ereigniswarteschlange erneut überprüft und die Ereignishandlerfunktion der HTTP-Anforderung ausführt, die als nächstes in der Warteschlange stand. Es ist wichtig zu beachten, dass die Ereigniswarteschlange nur verarbeitet wird, wenn der Ausführungsstapel leer ist.

Im folgenden Code wird beispielsweise die Behandlung des Ausführungsstapels und der Ereigniswarteschlange durch die Javascript-Engine erläutert.

function waitfunction() {
    var a = 5000 + new Date().getTime();
    while (new Date() < a){}
    console.log('waitfunction() context will be popped after this line');
}

function clickHandler() {
    console.log('click event handler...');   
}

document.addEventListener('click', clickHandler);


waitfunction(); //a new context for this function is created and placed on the execution stack
console.log('global context will be popped after this line');

Und

<html>
    <head>

    </head>
    <body>

        <script src="program.js"></script>
    </body>
</html>

Führen Sie nun die Webseite aus, klicken Sie auf die Seite und sehen Sie die Ausgabe auf der Konsole. Die Ausgabe wird sein

waitfunction() context will be popped after this line
global context will be emptied after this line
click event handler...

Die JavaScript-Engine führt den Code synchron aus, wie im Abschnitt zum Ausführungskontext erläutert. Der Browser stellt die Dinge asynchron in die Ereigniswarteschlange. Daher können die Funktionen, deren Ausführung sehr lange dauert, die Ereignisbehandlung unterbrechen. Dinge, die in einem Browser geschehen, wie Ereignisse, werden auf diese Weise von JavaScript behandelt. Wenn ein Listener ausgeführt werden soll, wird er von der Engine ausgeführt, wenn der Ausführungsstapel leer ist. Und Ereignisse werden in der Reihenfolge verarbeitet, in der sie auftreten. Im asynchronen Teil geht es also darum, was außerhalb der Engine geschieht, dh was die Engine tun soll, wenn diese externen Ereignisse eintreten.

JavaScript ist also immer synchron.


quelle
16
Diese Antwort ist sehr klar, es sollte mehr positive Stimmen bekommen.
Ranu
7
Mit Sicherheit die beste Erklärung für das asynchrone Verhalten von Javascript, das ich gelesen habe.
Charles Jaimet
1
Schöne Erklärung des Ausführungskontexts und der Warteschlange.
Divyanshu Maithani
1
Dazu müssen Sie natürlich ein wenig über den Ausführungskontextstapel lesen, und nur durch das Hinzufügen von Leerzeichen und Ereigniswarteschlange habe ich endlich das Gefühl, dass ich die Java-Skriptausschreibung deterministisch verstehe. Schlimmer ist, dass ich das Gefühl habe, dass es nur eine Seite zum Lesen braucht, aber ich finde es kaum irgendwo. Warum sagt es niemand einfach? Entweder wissen sie es nicht oder was? Aber ich habe das Gefühl, wenn ein js-Tutorial dies hätte, hätte es mir viel Zeit gespart. >: |
Marschall Handwerk
2
Perfekte Erklärung!
Julsy
100

JavaScript ist Single-Threaded und arbeitet die ganze Zeit an einer normalen synchronen Code-Flow-Ausführung.

Gute Beispiele für das asynchrone Verhalten von JavaScript sind Ereignisse (Benutzerinteraktion, Ajax-Anforderungsergebnisse usw.) und Timer, im Grunde genommen Aktionen, die jederzeit ausgeführt werden können.

Ich würde Ihnen empfehlen, einen Blick auf den folgenden Artikel zu werfen:

Dieser Artikel hilft Ihnen dabei, die Single-Thread-Natur von JavaScript zu verstehen und zu verstehen, wie Timer intern funktionieren und wie die asynchrone JavaScript-Ausführung funktioniert.

asynchron

CMS
quelle
Akzeptierte Antwort führt irre. Können wir in diesem Fall etwas unternehmen? /
Suraj Jain
7

Für jemanden, der wirklich versteht, wie JS funktioniert, scheint diese Frage nicht zutreffend zu sein, aber die meisten Leute, die JS verwenden, haben keine so tiefe Einsicht (und brauchen sie nicht unbedingt), und für sie ist dies ein ziemlich verwirrender Punkt versuche aus dieser Perspektive zu antworten.

JS ist synchron in der Art und Weise, wie sein Code ausgeführt wird. Jede Zeile läuft erst nach der Zeile, bevor sie abgeschlossen ist, und wenn diese Zeile danach eine Funktion aufruft, ist ect ...

Der Hauptverwirrungspunkt ergibt sich aus der Tatsache, dass Ihr Browser JS anweisen kann, jederzeit mehr Code auszuführen (ähnlich, wie Sie mehr JS-Code auf einer Seite von der Konsole aus ausführen können). Als Beispiel hat JS Rückruffunktionen, deren Zweck es ist, JS asynchron VERHALTEN zu lassen, damit weitere Teile von JS ausgeführt werden können, während auf eine ausgeführte JS-Funktion gewartet wird (dh ein GETAufruf), um eine Antwort zurückzugeben. JS wird weiter ausgeführt, bis Der Browser hat zu diesem Zeitpunkt eine Antwort. Die Ereignisschleife (Browser) führt den JS-Code aus, der die Rückruffunktion aufruft.

Da die Ereignisschleife (Browser) mehr JS eingeben kann, die zu jedem Zeitpunkt in diesem Sinne ausgeführt werden sollen, ist JS asynchron (die Hauptursachen für die Eingabe von JS-Code durch einen Browser sind Zeitüberschreitungen, Rückrufe und Ereignisse).

Ich hoffe, das ist klar genug, um jemandem zu helfen.

Yehuda Schwartz
quelle
4

Definition

Der Begriff "asynchron" kann in leicht unterschiedlichen Bedeutungen verwendet werden, was hier zu scheinbar widersprüchlichen Antworten führt, obwohl dies eigentlich nicht der Fall ist. Wikipedia über Asynchronität hat diese Definition:

Asynchronität bezieht sich in der Computerprogrammierung auf das Auftreten von Ereignissen unabhängig vom Hauptprogrammablauf und auf Möglichkeiten, mit solchen Ereignissen umzugehen. Dies können "externe" Ereignisse sein, wie das Eintreffen von Signalen oder von einem Programm ausgelöste Aktionen, die gleichzeitig mit der Programmausführung stattfinden, ohne dass das Programm blockiert, um auf Ergebnisse zu warten.

Nicht-JavaScript-Code kann solche "externen" Ereignisse in einige der Ereigniswarteschlangen von JavaScript einreihen. Aber das ist so weit wie es geht.

Keine Vorauszahlung

Es gibt keine externe Unterbrechung beim Ausführen von JavaScript-Code, um anderen JavaScript-Code in Ihrem Skript auszuführen. Teile von JavaScript werden nacheinander ausgeführt, und die Reihenfolge wird durch die Reihenfolge der Ereignisse in jeder Ereigniswarteschlange und die Priorität dieser Warteschlangen bestimmt.

Sie können beispielsweise absolut sicher sein, dass kein anderes JavaScript (im selben Skript) jemals ausgeführt wird, während der folgende Code ausgeführt wird:

let a = [1, 4, 15, 7, 2];
let sum = 0;
for (let i = 0; i < a.length; i++) {
    sum += a[i];
}

Mit anderen Worten, in JavaScript gibt es keine Vorrangstellung . Unabhängig davon, was sich in den Ereigniswarteschlangen befindet, muss die Verarbeitung dieser Ereignisse warten, bis dieser Code vollständig ausgeführt wurde. In der EcmaScript-Spezifikation heißt es in Abschnitt 8.4 Jobs und Jobwarteschlangen :

Die Ausführung eines Jobs kann nur initiiert werden, wenn kein Ausführungskontext ausgeführt wird und der Ausführungskontextstapel leer ist.

Beispiele für Asynchronität

Wie andere bereits geschrieben haben, gibt es verschiedene Situationen, in denen Asynchronität in JavaScript ins Spiel kommt, und es handelt sich immer um eine Ereigniswarteschlange, die nur dann zur Ausführung von JavaScript führen kann, wenn kein anderer JavaScript-Code ausgeführt wird:

  • setTimeout(): Der Agent (z. B. Browser) stellt ein Ereignis in eine Ereigniswarteschlange, wenn das Zeitlimit abgelaufen ist. Die Überwachung der Zeit und die Platzierung des Ereignisses in der Warteschlange erfolgt durch Nicht-JavaScript-Code. Sie können sich also vorstellen, dass dies parallel zur möglichen Ausführung von JavaScript-Code erfolgt. Der bereitgestellte Rückruf setTimeoutkann jedoch nur ausgeführt werden, wenn der aktuell ausgeführte JavaScript-Code vollständig ausgeführt wurde und die entsprechende Ereigniswarteschlange gelesen wird.

  • fetch(): Der Agent verwendet Betriebssystemfunktionen, um eine HTTP-Anforderung auszuführen und auf eingehende Antworten zu überwachen. Auch diese Nicht-JavaScript-Aufgabe wird möglicherweise parallel zu einem noch ausgeführten JavaScript-Code ausgeführt. Das Verfahren zur Auflösung von Versprechen, mit dem das von zurückgegebene Versprechen aufgelöst wird fetch(), kann jedoch nur ausgeführt werden, wenn das aktuell ausgeführte JavaScript vollständig ausgeführt wurde.

  • requestAnimationFrame(): Die Rendering-Engine des Browsers (kein JavaScript) platziert ein Ereignis in der JavaScript-Warteschlange, wenn es bereit ist, einen Malvorgang auszuführen. Wenn ein JavaScript-Ereignis verarbeitet wird, wird die Rückruffunktion ausgeführt.

  • queueMicrotask(): Platziert sofort ein Ereignis in der Mikrotask-Warteschlange. Der Rückruf wird ausgeführt, wenn der Aufrufstapel leer ist und dieses Ereignis verbraucht ist.

Es gibt viele weitere Beispiele, aber alle diese Funktionen werden von der Host-Umgebung bereitgestellt, nicht von Core-EcmaScript. Mit Core EcmaScript können Sie ein Ereignis synchron in eine Promise Job Queue mit platzieren Promise.resolve().

Sprachkonstrukte

EcmaScript stellt mehr Sprachkonstrukte , die Asynchronität Muster zu unterstützen, wie zum Beispiel yield, async, await. Aber es darf kein Fehler sein: Kein JavaScript-Code wird durch ein externes Ereignis unterbrochen . Die „Unterbrechung“ , dass yieldund awaitzu liefern scheint , ist nur eine gesteuerte, vorgegebene Art und Weise von einem Funktionsaufruf von der Rückkehr und die Wiederherstellung ihres Ausführungskontextes später entweder durch JS - Code (im Fall von yield) oder der Ereigniswarteschlange (im Fall von await).

DOM-Ereignisbehandlung

Wenn JavaScript-Code auf die DOM-API zugreift, kann dies in einigen Fällen dazu führen, dass die DOM-API eine oder mehrere synchrone Benachrichtigungen auslöst. Und wenn Ihr Code einen Ereignishandler hat, der das abhört, wird er aufgerufen.

Dies kann als vorbeugende Parallelität erscheinen, ist es jedoch nicht: Sobald Ihre Event-Handler zurückkehren, wird schließlich auch die DOM-API zurückgegeben und der ursprüngliche JavaScript-Code wird fortgesetzt.

In anderen Fällen sendet die DOM-API nur ein Ereignis in der entsprechenden Ereigniswarteschlange aus, und JavaScript nimmt es auf, sobald der Aufrufstapel geleert wurde.

Siehe synchrone und asynchrone Ereignisse

Trincot
quelle
0

Ist in allen Fällen synchron.

Beispiel für das Blockieren von Threads mit Promises:

  const test = () => new Promise((result, reject) => {
    const time = new Date().getTime() + (3 * 1000);

    console.info('Test start...');

    while (new Date().getTime() < time) {
      // Waiting...
    }

    console.info('Test finish...');
  });

  test()
    .then(() => console.info('Then'))
    .finally(() => console.info('Finally'));

  console.info('Finish!');

Die Ausgabe wird sein:

Test start...
Test finish...
Finish!
Eduardo Cuomo
quelle