Warum bricht setTimeout meine Schleife nicht ab?


75

Ich habe mich gefragt, wie oft eine JavaScript- whileAnweisung (in der Chrome-Konsole) eine Variable in einer Millisekunde inkrementieren kann. Deshalb habe ich dieses Snippet schnell direkt in die Konsole geschrieben:

var run = true, i = 0;
setTimeout(function(){ run = false; }, 1);
while(run){ i++; }

Das Problem ist, dass es für immer läuft.
Warum passiert das und wie kann ich es lösen?


27
Weil die while-Schleife keine Prozessorzeit für das Timeout
liefert

Vielleicht wäre dies nützlich zu lesen: stackoverflow.com/questions/21463377/…
jillro

4
Warum ist diese Frage so hoch bewertet?

6
@remyabel Ich denke, das liegt daran, dass es ein großartiger Fall ist, um zu demonstrieren, wie der Single-Thread funktioniert. Bei JS wird häufig davon ausgegangen, dass ein Intervall / Timeout den Kontrollfluss sofort unterbricht und ausgeführt wird, wenn dies in einigen Fällen - wie oben - niemals der Fall ist.
MickMalone1983

2
Als Erweiterung der folgenden Antworten würden Sie mit einer verzögerten rekursiven Funktion das tun, was Sie erwartet hatten:var run = true, i = 0; function loop() { i++; if (run) setTimeout(loop, 0); }; setTimeout(function() { run = false }, 1); loop();
Izkata

Antworten:


80

Dies kommt auf die Ein-Thread-Natur von JavaScript 1 zurück . Was passiert ist so ziemlich das:

  1. Ihre Variablen werden zugewiesen.
  2. Sie planen eine Funktion zum Einstellen run = false. Dies soll ausgeführt werden, nachdem die aktuelle Funktion ausgeführt wurde (oder was auch immer aktuell aktiv ist).
  3. Sie haben Ihre Endlosschleife und bleiben in der aktuellen Funktion.
  4. Nach Ihrer Endlosschleife ( nie ) wird der setTimeout()Rückruf ausgeführt und run=false.

Wie Sie sehen können, wird ein setTimeout()Ansatz hier nicht funktionieren. Sie können dies umgehen, indem Sie die Zeit in der whileBedingung überprüfen. Dies wird jedoch Ihre tatsächliche Messung beeinträchtigen.

1 Zumindest für praktischere Zwecke können Sie es als Single-Threaded sehen. Eigentlich gibt es eine sogenannte "Ereignisschleife". In dieser Schleife werden alle Funktionen in die Warteschlange gestellt, bis sie ausgeführt werden. Wenn Sie eine neue Funktion in die Warteschlange stellen, wird sie an der entsprechenden Position in dieser Warteschlange abgelegt. Nachdem die aktuelle Funktion beendet wurde, nimmt die Engine die nächste Funktion aus der Warteschlange (in Bezug auf die eingeführten Timings, z. B. von setTimeout()und führt sie aus.
Infolgedessen wird zu jedem Zeitpunkt nur eine Funktion ausgeführt, wodurch die Ausführung hübsch wird Es gibt einige Ausnahmen für Ereignisse, die im folgenden Link erläutert werden.


Als Referenz:


3
Wie @Sirko angedeutet hat, speichern Sie den aktuellen Zeitstempel in einer Variablen, um den gewünschten Effekt zu erzielen. Subtrahieren Sie var start = (new Date()).getTime();dann in der whileAnweisung diese Zeit von der aktuellen Zeit und prüfen Sie, ob die resultierende Zahl kleiner als die beabsichtigte Verzögerung ist . while ((new Date()).getTime() - start < delay).
Nutzloser Code

1
Wenn Sie nur sehen möchten, wie schnell Chrome ausgeführt wird, stellen Sie die while-Schleife so ein, dass sie in etwa 10.000 Schritten ausgeführt wird. Stellen Sie eine Variable wie die Startzeit vor der Schleife und dann die aktuelle Zeit nach der Schleife ein und vergleichen Sie die beiden.
SyntaxLAMP

"Manchmal" ist genau dann, wenn der Stapel leer ist.
Jillro

Es liegt nicht wirklich an der Ein-Thread-Natur von Javascript. Selbst wenn es sehr dumm wäre, könnte die Implementierung doch "nach Nachrichten zwischen den einzelnen Anweisungen suchen" sein, da JS eine interpretierte Sprache ist. Dies liegt an der Implementierung der Ereignisschleife, die aufgerufen wird, wenn der Stapel leer ist.
Jillro

@ Guilro Was das "manchmal" betrifft - das Konzept heißt Ironie und ich denke, ich habe es mit dem hervorgehobenen "nie" geklärt. Was die "Nachricht zwischen" betrifft - jede JS-Engine, die ich kenne, ist im Grunde genommen Single-Threaded (außer Worker). Weitere Informationen finden Sie unter stackoverflow.com/a/2734311/1169798 . Übrigens greifen die meisten modernen Engines nur dann auf die direkte Interpretation von JS zurück, wenn ihre erweiterten Funktionen fehlschlagen.
Sirko

24

JavaScript ist Single-Threaded, sodass nichts anderes ausgeführt wird, während Sie sich in der Schleife befinden.


2
Seien Sie vorsichtig, wenn Sie JavaScript Single-Threaded aufrufen, da es Multithreading verarbeiten kann. Die Standardausführung erfolgt jedoch mit einem Thread.
zzzzBov

19

Um die wahre Geschwindigkeit von Chrome beizubehalten, ohne ständig die Zeit zur Berechnung der Geschwindigkeit abrufen zu müssen, können Sie diesen JS-Code ausprobieren:

var start = new Date().getTime()
var i = 0
while(i<1000000)
{
    i++
}
var end = new Date().getTime()
var delta = end-start
var speed = i/delta
console.log(speed + " loops per millisecond")

4

Javascript ist Single-Threaded, dh es wird jeweils nur eine Anweisung nacheinander ausgeführt.

Das Ereignissystem wird wie in vielen anderen Sprachen und Bibliotheken von einer Ereignisschleife verwaltet. Die Ereignisschleife ist im Grunde eine Schleife, die bei jeder Iteration nach Nachrichten in der Warteschlange sucht und Ereignisse auslöst.

In Javascript (wie in vielen Sprachen, die dieses Muster implementieren) wird die Ereignisschleife aufgerufen, wenn der Stapel leer ist, dh wenn alle Funktionen am Ende des Programmcodes zurückgegeben werden, dh.

Ihr "echtes" Programm sieht hinter den Kulissen ungefähr so ​​aus:

var run = true, i = 0;
setTimeout(function(){ run = false; }, 1);
while(run){ i++; }

while(true) {
/*
 * check for new messages in the queue and dispatch
 * events if there are some
 */
  processEvents();
}

Die Nachricht von der Uhr, dass das Zeitlimit abgelaufen ist, wird also nie verarbeitet.

Weitere Informationen zur Ereignisschleife finden Sie unter: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/EventLoop


Natürlich ist es etwas komplexer. Sehen Sie sich hier die folgenden Beispiele an: Ist JavaScript garantiert Single-Threaded? ( tl; dr: In einigen Browser-Engines sind einige externe Ereignisse nicht von der Ereignisschleife abhängig und werden sofort ausgelöst, wenn sie auftreten, wodurch die aktuelle Aufgabe verhindert wird. Dies ist jedoch bei setTimeout nicht der Fall, bei dem lediglich eine Nachricht zur Warteschlange hinzugefügt wird und feuert nie sofort.)


2

Die While-Schleife greift nicht auf das setTimeout zu. Sie haben Code, der setzt, dass true ausgeführt wird, und dann wird er niemals false.


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.