1119 Stimmen

Warum ist setTimeout(fn, 0) manchmal nützlich?

Ich bin kürzlich auf einen ziemlich fiesen Fehler gestoßen, bei dem der Code eine <select> dynamisch über JavaScript. Dieses dynamisch geladene <select> einen vorgewählten Wert hatte. Im IE6 hatten wir bereits Code, um den ausgewählten Wert zu fixieren <option> denn manchmal ist die <select> 's selectedIndex Wert nicht mit dem gewählten Wert übereinstimmen würde <option> 's index Attribut, wie unten dargestellt:

field.selectedIndex = element.index;

Dieser Code funktionierte jedoch nicht. Auch wenn das Feld selectedIndex richtig eingestellt war, wurde der falsche Index ausgewählt. Wenn ich jedoch einen alert() Anweisung zum richtigen Zeitpunkt die richtige Option ausgewählt werden würde. Da ich dachte, dass es sich um ein Zeitproblem handeln könnte, habe ich etwas Zufälliges ausprobiert, das ich bereits im Code gesehen hatte:

var wrapFn = (function() {
    var myField = field;
    var myElement = element;

    return function() {
        myField.selectedIndex = myElement.index;
    }
})();
setTimeout(wrapFn, 0);

Und es hat funktioniert!

Ich habe eine Lösung für mein Problem gefunden, aber ich weiß nicht genau, warum sie mein Problem behebt. Hat jemand eine offizielle Erklärung? Welches Browserproblem vermeide ich, indem ich meine Funktion "später" aufrufe, indem ich setTimeout() ?

2 Stimmen

Die meisten Fragen beschreiben, warum es nützlich ist. Wenn Sie wissen wollen, warum das so ist, lesen Sie meine Antwort: stackoverflow.com/a/23747597/1090562

35 Stimmen

Philip Roberts erklärt dies in seinem Vortrag "What the heck is the event loop?" auf die bestmögliche Weise. youtube.com/watch?v=8aGhZQkoFbQ

4 Stimmen

Wenn Sie es eilig haben, hier beginnt der Teil des Videos, in dem er genau auf die Frage eingeht: youtu.be/8aGhZQkoFbQ?t=14m54s . Trotzdem ist das ganze Video auf jeden Fall sehenswert.

26voto

Vladimir Kornea Punkte 3872

Hier gibt es widersprüchliche Antworten, und ohne Beweise kann man nicht wissen, wem man glauben soll. Hier ist der Beweis, dass @DVK recht hat und @SalvadorDali unrecht hat. Letzterer behauptet:

"Und das ist der Grund: Es ist nicht möglich, setTimeout mit einer Zeitverzögerung Verzögerung von 0 Millisekunden zu haben. Der Mindestwert wird vom Browser bestimmt Browser bestimmt, und er beträgt nicht 0 Millisekunden. Historisch gesehen setzen die Browser dieses Minimum auf 10 Millisekunden, aber die HTML5-Spezifikationen und moderne Browser haben es auf 4 Millisekunden festgelegt."

Die Mindestzeitspanne von 4 ms ist für das Geschehen irrelevant. Was wirklich passiert, ist, dass setTimeout die Callback-Funktion an das Ende der Ausführungswarteschlange schiebt. Wenn Sie nach setTimeout(callback, 0) blockierenden Code haben, der mehrere Sekunden zur Ausführung benötigt, wird der Callback mehrere Sekunden lang nicht ausgeführt, bis der blockierende Code beendet ist. Probieren Sie diesen Code aus:

function testSettimeout0 () {
    var startTime = new Date().getTime()
    console.log('setting timeout 0 callback at ' +sinceStart())
    setTimeout(function(){
        console.log('in timeout callback at ' +sinceStart())
    }, 0)
    console.log('starting blocking loop at ' +sinceStart())
    while (sinceStart() < 3000) {
        continue
    }
    console.log('blocking loop ended at ' +sinceStart())
    return // functions below
    function sinceStart () {
        return new Date().getTime() - startTime
    } // sinceStart
} // testSettimeout0

Die Ausgabe ist:

setting timeout 0 callback at 0
starting blocking loop at 5
blocking loop ended at 3000
in timeout callback at 3033

1 Stimmen

Ihre Antwort beweist gar nichts. Sie zeigt nur, dass der Computer auf Ihrem Rechner in einer bestimmten Situation ein paar Zahlen ausgibt. Um etwas zu beweisen, braucht man etwas mehr als nur ein paar Zeilen Code und ein paar Zahlen.

7 Stimmen

@SalvadorDali, ich glaube, mein Beweis ist für die meisten Menschen klar genug, um ihn zu verstehen. Ich glaube, Sie fühlen sich defensiv und haben sich nicht die Mühe gemacht, ihn zu verstehen. Ich werde gerne versuchen, ihn zu erläutern, aber ich weiß nicht, was Sie nicht verstehen. Versuchen Sie, den Code auf Ihrem eigenen Rechner auszuführen, wenn Sie meine Ergebnisse anzweifeln.

22voto

Diese beiden am besten bewerteten Antworten sind beide falsch. Sehen Sie sich die MDN-Beschreibung des Gleichzeitigkeitsmodells und der Ereignisschleife an und es sollte klar werden, was vor sich geht (die MDN-Ressource ist ein echtes Juwel). Und einfach mit setTimeout kann neben der "Lösung" dieses kleinen Problems auch unerwartete Probleme in Ihrem Code verursachen.

Was ist eigentlich Hier geht es nicht darum, dass "der Browser vielleicht noch nicht ganz fertig ist, weil die Gleichzeitigkeit nicht gegeben ist", oder um etwas, das auf "jede Zeile ist ein Ereignis, das am Ende der Warteschlange hinzugefügt wird" basiert.

El jsfiddle von DVK veranschaulicht zwar ein Problem, aber seine Erklärung dafür ist nicht korrekt.

Was in seinem Code passiert, ist, dass er zuerst einen Event-Handler an die click Ereignis auf der #do Taste.

Wenn Sie dann tatsächlich auf die Schaltfläche klicken, wird ein message erstellt, die auf die Ereignisbehandlungsfunktion verweist, die der Datei message queue . Wenn die event loop diese Nachricht erreicht, erstellt es eine frame auf dem Stapel, mit dem Funktionsaufruf des Click-Event-Handlers im jsfiddle.

Und genau hier wird es interessant. Wir sind so daran gewöhnt, Javascript als asynchron zu betrachten, dass wir dazu neigen, diese kleine Tatsache zu übersehen: Jeder Frame muss vollständig ausgeführt werden, bevor der nächste Frame ausgeführt werden kann. . Keine Gleichzeitigkeit, Leute.

Was bedeutet das? Es bedeutet, dass jede Funktion, die von der Nachrichtenwarteschlange aus aufgerufen wird, die Warteschlange blockiert, bis der von ihr erzeugte Stapel geleert wurde. Oder, allgemeiner ausgedrückt, sie blockiert, bis die Funktion zurückgekehrt ist. Und sie blockiert alles einschließlich DOM-Rendering-Operationen, Scrollen und dergleichen. Wenn Sie eine Bestätigung wünschen, versuchen Sie einfach, die Dauer der lang laufenden Operation im Fiddle zu verlängern (z. B. lassen Sie die äußere Schleife noch 10 Mal laufen), und Sie werden feststellen, dass Sie die Seite nicht mehr scrollen können, während sie läuft. Wenn er lange genug läuft, wird Ihr Browser Sie fragen, ob Sie den Prozess beenden wollen, weil die Seite nicht mehr reagiert. Der Frame wird ausgeführt, und die Ereignisschleife und die Nachrichtenwarteschlange bleiben stecken, bis er beendet ist.

Warum also dieser Nebeneffekt, dass der Text nicht aktualisiert wird? Denn während Sie haben den Wert des Elements im DOM geändert - Sie können console.log() seinen Wert unmittelbar nach der Änderung und sehen, dass er hat geändert wurde (was zeigt, warum DVKs Erklärung nicht korrekt ist) - der Browser wartet darauf, dass der Stack aufgebraucht ist (die on Handler-Funktion zurückzugeben) und damit die Nachricht zu beenden, so dass sie schließlich die Nachricht ausführen kann, die von der Laufzeit als Reaktion auf unsere Mutationsoperation hinzugefügt wurde, und um diese Mutation in der Benutzeroberfläche widerzuspiegeln.

Der Grund dafür ist, dass wir darauf warten, dass der Code fertiggestellt wird. Wir haben nicht gesagt: "Jemand holt das und ruft dann diese Funktion mit den Ergebnissen auf, danke, und jetzt bin ich fertig, also kehre ich zurück, mach was auch immer jetzt", wie wir es normalerweise mit unserem ereignisbasierten asynchronen Javascript tun. Wir geben eine Click-Event-Handler-Funktion ein, wir aktualisieren ein DOM-Element, wir rufen eine andere Funktion auf, die andere Funktion arbeitet lange und kehrt dann zurück, wir aktualisieren dann das gleiche DOM-Element, und dann kehren wir von der Ausgangsfunktion zurück, wodurch der Stapel effektiv geleert wird. Und dann kann der Browser zur nächsten Nachricht in der Warteschlange gelangen, bei der es sich durchaus um eine Nachricht handeln kann, die von uns durch Auslösen eines internen Ereignisses vom Typ "on-DOM-mutation" erzeugt wurde.

Die Benutzeroberfläche des Browsers kann die Benutzeroberfläche nicht aktualisieren (oder will es nicht), bis der aktuell ausgeführte Frame abgeschlossen ist (die Funktion zurückgekehrt ist). Ich persönlich denke, dass dies eher durch Design als durch Beschränkung bedingt ist.

Warum ist die setTimeout Sache dann funktionieren? Es funktioniert, weil es den Aufruf der langlaufenden Funktion effektiv aus seinem eigenen Frame entfernt und die Ausführung zu einem späteren Zeitpunkt in der window Kontext, so dass sie selbst sofort zurückkehren und erlauben der Nachrichtenwarteschlange, andere Nachrichten zu verarbeiten. Und die Idee ist, dass die UI "auf Update"-Nachricht, die von uns in Javascript ausgelöst wurde, wenn der Text im DOM zu ändern ist jetzt vor der Nachricht für die lang laufende Funktion in der Warteschlange, so dass die UI-Aktualisierung geschieht, bevor wir für eine lange Zeit blockieren.

Beachten Sie, dass a) die langlaufende Funktion noch blockiert alles, wenn es ausgeführt wird, und b) Sie sind nicht garantiert, dass das UI-Update tatsächlich vor ihm in der Nachrichtenwarteschlange ist. In meinem Chrome-Browser vom Juni 2018 wird ein Wert von 0 behebt nicht das Problem, das die Fiddle demonstriert - 10 schon. Ich bin tatsächlich ein wenig erstickt durch diese, denn es scheint logisch für mich, dass die UI-Update-Nachricht sollte vor ihr in der Warteschlange sein, da seine Auslöser vor der Planung der lang laufenden Funktion ausgeführt wird, um "später" ausgeführt werden. Aber vielleicht gibt es einige Optimierungen in der V8-Engine, die stören können, oder vielleicht ist mein Verständnis einfach nicht ausreichend.

Okay, was ist also das Problem bei der Verwendung von setTimeout und was ist die bessere Lösung für diesen speziellen Fall?

Zunächst einmal ist das Problem bei der Verwendung von setTimeout auf jeden Event-Handler wie diesen, um zu versuchen, ein anderes Problem zu lindern, ist anfällig für die Verwirrung mit anderen Code. Hier ist ein Beispiel aus dem wirklichen Leben aus meiner Arbeit:

Ein Kollege, in einem falsch informierten Verständnis über die Ereignisschleife, versucht zu "Thread" Javascript, indem einige Vorlage Rendering-Code verwenden setTimeout 0 für seine Darstellung. Er ist nicht mehr hier, um zu fragen, aber ich kann vermuten, dass er vielleicht Timer eingefügt hat, um die Rendering-Geschwindigkeit zu messen (was die Unmittelbarkeit der Rückgabe von Funktionen wäre) und festgestellt hat, dass die Verwendung dieses Ansatzes für blitzschnelle Antworten von dieser Funktion sorgen würde.

Das erste Problem liegt auf der Hand: Sie können kein JavaScript-Threading durchführen, so dass Sie hier nichts gewinnen, während Sie eine Verschleierung hinzufügen. Zweitens haben Sie jetzt effektiv das Rendern einer Vorlage von dem Stapel möglicher Ereignis-Listener abgetrennt, die erwarten könnten, dass genau diese Vorlage gerendert wurde, während sie es vielleicht gar nicht war. Das tatsächliche Verhalten dieser Funktion ist nun nicht mehr deterministisch, ebenso wie - ohne es zu wissen - jede Funktion, die sie ausführt oder von ihr abhängt. Man kann fundierte Vermutungen anstellen, aber man kann das Verhalten nicht richtig codieren.

Die "Lösung" beim Schreiben eines neuen Ereignis-Handlers, der von seiner Logik abhing, bestand darin, dass auch verwenden. setTimeout 0 . Aber das ist keine Lösung, es ist schwer zu verstehen, und es macht keinen Spaß, Fehler zu beheben, die durch solchen Code verursacht werden. Manchmal gibt es überhaupt kein Problem, ein anderes Mal scheitert es ständig, und dann wieder funktioniert es manchmal und bricht sporadisch ab, je nach aktueller Leistung der Plattform und was auch immer sonst gerade los ist. Aus diesem Grund würde ich persönlich davon abraten, diesen Hack zu verwenden (er ist ein Hack, und wir sollten alle wissen, dass es einer ist), es sei denn, man weiß wirklich, was man tut und welche Konsequenzen das hat.

Was aber kann wir stattdessen tun? Nun, wie der zitierte MDN-Artikel vorschlägt, können Sie entweder die Arbeit in mehrere Nachrichten aufteilen (wenn Sie können), so dass andere Nachrichten, die in der Warteschlange stehen, mit Ihrer Arbeit verschachtelt und ausgeführt werden können, während sie läuft, oder Sie verwenden einen Webworker, der parallel zu Ihrer Seite läuft und Ergebnisse zurückgibt, wenn er mit seinen Berechnungen fertig ist.

Oh, und wenn Sie jetzt denken: "Könnte ich nicht einfach einen Rückruf in die lang laufende Funktion einfügen, um sie asynchron zu machen?", dann nein. Der Callback macht die Funktion nicht asynchron, sie muss immer noch den langlaufenden Code ausführen, bevor sie explizit Ihren Callback aufruft.

3 Stimmen

Scheinbar der einzige gültige und vollständige Kommentar auf dieser ganzen Seite

21voto

aderchox Punkte 1534

Wenn Sie nicht zuschauen wollen ein ganzes Video Hier eine einfache Erklärung der Dinge, die man verstehen muss, um die Antwort auf diese Frage verstehen zu können:

  1. JavaScript ist single-threaded Das bedeutet, dass es immer nur eine Sache auf einmal macht, wenn es läuft.
  2. Aber die Umgebungen, in denen JavaScript ausgeführt wird, können Multi-Thread-fähig sein. So sind z.B. Browser oft Multi-Thread-Wesen, d.h. sie können mehrere Dinge gleichzeitig tun. So können sie JavaScript ausführen und sich gleichzeitig um andere Dinge kümmern.

Von nun an sprechen wir über JavaScript "in Browsern". Dinge wie setTimeout sind in der Tat Browser-Sachen und nicht Teil des JavaScript selbst.

  1. Der Grund, warum JavaScript asynchron laufen kann, ist der Multithreading-Browser! Außer dem Hauptbereich, den Javascript nutzt (genannt der Aufrufstapel ), um die einzelnen Codezeilen zu platzieren und sie nacheinander auszuführen, bieten Browser auch JavaScript einen weiteren Platz, um Dinge zu platzieren.

Nennen wir diesen anderen Raum nun der zweite Raum .

  1. Nehmen wir an fn ist eine Funktion. Wichtig ist hier, dass man versteht, dass fn(); Aufruf ist nicht gleich dem setTimeout(fn, 0); aufrufen wie weiter unten erläutert wird.

Anstelle einer 0 Verzögerung, nehmen wir zunächst eine andere Verzögerung an, z.B. 5000 Millisekunden: setTimeout(fn, 5000); . Es ist wichtig zu beachten, dass es sich hierbei immer noch um einen "Funktionsaufruf" handelt, der also in den Hauptbereich eingefügt und nach Beendigung wieder entfernt werden muss, aber halt, wir wollen keine langwierige und langweilige Verzögerung von 5 Sekunden. Das würde den Hauptspeicher blockieren und verhindert, dass JavaScript in der Zwischenzeit irgendetwas anderes ausführen kann.

Zum Glück haben die Browser-Designer sie nicht so konzipiert. Stattdessen wird dieser Aufruf( setTimeout(fn, 5000); ) ist erledigt sofort . Dies ist sehr wichtig: Selbst mit der Verzögerung von 5000 Millisekunden ist dieser Funktionsaufruf in einem Augenblick abgeschlossen! Was wird als nächstes passieren? Sie wird aus dem Hauptraum entfernt. Wo wird es aufgestellt? (denn wir wollen es nicht verlieren). Sie haben es vielleicht schon erraten: Der Browser Hört diesen Aufruf und setzt ihn auf das zweite Feld. enter image description here

Der Browser merkt sich die 5 Sekunden Verzögerung und sobald es durchgelaufen ist, schaut es sich den Hauptraum an, und "WENN ES LEER IST", setzt es die fn(); darauf zurückkommen . So ist die setTimeout funktioniert.

Also zurück zum setTimeout(fn, 0) Auch wenn die Verzögerung gleich Null ist, handelt es sich dennoch um einen Aufruf an den Browser, und der Browser hört ihn sofort, nimmt ihn auf und legt ihn auf den zweiten Platz und dann wieder auf den Hauptplatz erst, wenn der Hauptspeicher wieder leer ist, und nicht wirklich 0 Millisekunden später .

Ich empfehle wirklich, sich auch dieses Video anzusehen, da er es wirklich gut erklärt und die technischen Dinge besser erklärt.

0 Stimmen

19voto

Pointy Punkte 387467

Ein Grund dafür ist, die Ausführung von Code auf eine separate, nachfolgende Ereignisschleife zu verschieben. Wenn man auf ein Browser-Ereignis reagiert (z.B. Mausklick), ist es manchmal notwendig, nur die folgenden Operationen auszuführen nach wird das aktuelle Ereignis verarbeitet. Die Website setTimeout() ist die einfachste Möglichkeit, dies zu tun.

bearbeiten Jetzt, wo wir 2015 haben, sollte ich erwähnen, dass es auch requestAnimationFrame() was zwar nicht genau dasselbe ist, aber doch nahe genug an setTimeout(fn, 0) dass es eine Erwähnung wert ist.

0 Stimmen

Dies ist genau einer der Orte, an denen ich gesehen habe, dass es verwendet wird =)

0 Stimmen

FYI: Diese Antwort wurde hier zusammengeführt von stackoverflow.com/questions/4574940/

2 Stimmen

die Ausführung von Code auf eine separate, nachfolgende Ereignisschleife zu verschieben : Wie findet man heraus, dass ein anschließende Ereignisschleife ? Wie finden Sie heraus, was eine aktuelle Ereignisschleife ist? Woher wissen Sie, in welcher Ereignisschleife Sie sich gerade befinden?

13voto

Salvador Dali Punkte 197375

Dies ist eine alte Frage mit alten Antworten. Ich wollte einen neuen Blick auf dieses Problem hinzufügen und beantworten, warum dies geschieht und nicht, warum dies nützlich ist.

Sie haben also zwei Funktionen:

var f1 = function () {    
   setTimeout(function(){
      console.log("f1", "First function call...");
   }, 0);
};

var f2 = function () {
    console.log("f2", "Second call...");
};

und rufen sie dann in der folgenden Reihenfolge auf f1(); f2(); nur um zu sehen, dass die zweite zuerst ausgeführt wird.

Und das ist der Grund: Es ist nicht möglich, eine setTimeout mit einer Zeitverzögerung von 0 Millisekunden. Die Der Mindestwert wird vom Browser bestimmt und sie beträgt nicht 0 Millisekunden. Historisch Browser setzt dieses Minimum auf 10 Millisekunden, aber die HTML5-Spezifikationen und bei modernen Browsern ist sie auf 4 Millisekunden eingestellt.

Wenn die Verschachtelungsebene größer als 5 und die Zeitüberschreitung kleiner als 4 ist, dann erhöhen Sie die Zeitüberschreitung auf 4.

Auch von mozilla:

Um ein Timeout von 0 ms in einem modernen Browser zu implementieren, können Sie window.postMessage() wie beschrieben verwenden aquí .

P.S. Die Informationen werden nach der Lektüre des folgenden Textes aufgenommen Artikel .

1 Stimmen

@user2407309 Machen Sie Witze? Sie meinen, dass die HTML5-Spezifikation falsch ist und Sie richtig liegen? Lesen Sie die Quellen, bevor Sie abwerten und starke Behauptungen aufstellen. Meine Antwort basiert auf der HTML-Spezifikation und den historischen Aufzeichnungen. Anstatt die Antwort zu geben, die immer wieder das Gleiche erklärt, habe ich etwas Neues hinzugefügt, etwas, das in früheren Antworten nicht gezeigt wurde. Ich sage nicht, dass dies der einzige Grund ist, ich zeige nur etwas Neues.

1 Stimmen

Das ist falsch: "Und hier ist der Grund: Es ist nicht möglich, setTimeout mit einer Zeitverzögerung von 0 Millisekunden zu haben." Das ist nicht der Grund. Die Verzögerung von 4 ms ist irrelevant für den Grund setTimeout(fn,0) nützlich ist.

0 Stimmen

@user2407309 es kann leicht geändert werden in "um die von anderen genannten Gründe zu ergänzen, ist es nicht möglich ....". Es ist also lächerlich, den Beitrag deswegen abzulehnen, vor allem wenn die eigene Antwort nichts Neues sagt. Eine kleine Änderung würde ausreichen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X