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.

990voto

staticsan Punkte 29057

In der Frage gab es eine Rennbedingung zwischen:

  1. Der Versuch des Browsers, die Dropdown-Liste zu initialisieren, um den ausgewählten Index zu aktualisieren, und
  2. Ihr Code zum Setzen des ausgewählten Index

Ihr Code hat dieses Rennen immer wieder gewonnen und versucht, die Dropdown-Auswahl festzulegen, bevor der Browser bereit war, was dazu führte, dass der Fehler auftrat.

Dieses Rennen existierte, weil JavaScript eine einzelner Ausführungsstrang die mit dem Rendering der Seite geteilt wird. Die Ausführung von JavaScript blockiert also die Aktualisierung des DOM.

Ihre Abhilfe war:

setTimeout(callback, 0)

Aufrufen von setTimeout mit einem Rückruf und Null als zweitem Argument wird der Rückruf zur Ausführung geplant asynchron nach der kürzest möglichen Verzögerung, die etwa 10 ms beträgt, wenn die Registerkarte den Fokus hat und der JavaScript-Thread der Ausführung nicht beschäftigt ist.

Die Lösung des OP bestand daher darin, die Einstellung des gewählten Index um etwa 10 ms zu verzögern. Dies gab dem Browser die Möglichkeit, das DOM zu initialisieren und den Fehler zu beheben.

Jede Version des Internet Explorers wies eigenartige Verhaltensweisen auf, so dass diese Art von Umgehung manchmal notwendig war. Alternativ könnte es sich auch um einen echten Fehler in der Codebasis des Betreibers handeln.


Siehe Philip Roberts Vortrag "Was zum Teufel ist die Ereignisschleife?" für eine ausführlichere Erklärung.

326 Stimmen

Die Lösung ist, die JavaScript-Ausführung anzuhalten, damit die Rendering-Threads aufholen können. Nicht ganz richtig, was setTimeout tut, ist ein neues Ereignis zur Browser-Ereigniswarteschlange hinzufügen und die Rendering-Engine ist bereits in dieser Warteschlange (nicht ganz richtig, aber nahe genug), so dass es vor dem setTimeout-Ereignis ausgeführt wird.

55 Stimmen

Ja, das ist eine viel ausführlichere und viel richtigere Antwort. Aber meine ist "korrekt genug", damit die Leute verstehen, warum der Trick funktioniert.

3 Stimmen

@DavidMulder, bedeutet es Browser parse css und Rendering in einem anderen Thread von Javascript Ausführung Thread?

731voto

DVK Punkte 123218

Vorwort:

Einige der anderen Antworten sind zwar richtig, aber sie veranschaulichen nicht wirklich, was das zu lösende Problem ist.

Aus diesem Grund stelle ich eine ausführliche Erläuterung der Funktionen des Browsers und der Verwendung setTimeout() hilft . Es sieht lang aus, ist aber eigentlich sehr einfach und unkompliziert - ich habe es nur sehr ausführlich gemacht.

UPDATE: Ich habe ein JSFiddle zur Live-Demonstration der folgenden Erklärung erstellt: http://jsfiddle.net/C2YBE/31/ . Viele danke an @ThangChung, der den Anstoß dazu gegeben hat.

UPDATE2: Nur für den Fall, dass die JSFiddle-Website stirbt oder den Code löscht, habe ich den Code dieser Antwort ganz am Ende hinzugefügt.


DETAILS :

Stellen Sie sich eine Webanwendung mit einer Schaltfläche "etwas tun" und einem Ergebnis-Div vor.

El onClick Handler für die Schaltfläche "Etwas tun" ruft eine Funktion "LongCalc()" auf, die 2 Dinge tut:

  1. Macht eine sehr lange Berechnung (z.B. 3 Minuten)

  2. Druckt die Ergebnisse der Berechnung in das Ergebnisfeld.

Nun, Ihre Benutzer testen dies, klicken auf die Schaltfläche "etwas tun", und die Seite sitzt da und tut scheinbar 3 Minuten lang nichts, sie werden unruhig, klicken wieder auf die Schaltfläche, warten 1 Minute, nichts passiert, klicken wieder auf die Schaltfläche...

Das Problem liegt auf der Hand - Sie wollen ein "Status"-DIV, das anzeigt, was vor sich geht. Schauen wir mal, wie das geht.


Sie fügen also ein (zunächst leeres) "Status"-DIV hinzu und ändern die onclick Handler (Funktion LongCalc() ), um 4 Dinge zu tun:

  1. Den Status "Berechnen... kann ~3 Minuten dauern" in den Status DIV einfügen

  2. Macht eine sehr lange Berechnung (z.B. 3 Minuten)

  3. Druckt die Ergebnisse der Berechnung in das Ergebnisfeld.

  4. Den Status "Berechnung abgeschlossen" in den Status DIV einfügen

Und Sie geben die App gerne an die Nutzer weiter, damit sie sie erneut testen können.

Sie kommen zu Ihnen zurück und sehen sehr verärgert aus. Und erklären, dass sie auf die Schaltfläche geklickt haben, der Status DIV wurde nie mit dem Status "Calculating..." aktualisiert!!!


Sie kratzen sich am Kopf, fragen auf StackOverflow herum (oder lesen Docs oder Google) und erkennen das Problem:

Der Browser legt alle seine "TODO"-Aufgaben (sowohl UI-Aufgaben als auch JavaScript-Befehle), die aus Ereignissen resultieren, in einem einzelne Warteschlange . Und leider ist das erneute Zeichnen des "Status" DIV mit dem neuen "Calculating..."-Wert ein separates TODO, das ans Ende der Warteschlange gehört!

Hier finden Sie eine Aufschlüsselung der Ereignisse während des Benutzertests und den Inhalt der Warteschlange nach jedem Ereignis:

  • Warteschlange: [Empty]
  • Veranstaltung: Klicken Sie auf die Schaltfläche. Warteschlange nach Ereignis: [Execute OnClick handler(lines 1-4)]
  • Veranstaltung: Ausführen der ersten Zeile im OnClick-Handler ( [Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value] . Bitte beachten Sie, dass die DOM-Änderungen zwar augenblicklich erfolgen, Sie aber ein neues Ereignis benötigen, das durch die DOM-Änderung ausgelöst wird und am Ende der Warteschlange steht, um das entsprechende DOM-Element neu zu zeichnen .
  • PROBLEM!!! PROBLEM!!! Einzelheiten werden weiter unten erläutert.
  • Veranstaltung: Zweite Zeile im Handler ausführen (Berechnung). Warteschlange nach: [Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value] .
  • Ereignis: Ausführen der 3. Zeile im Handler (Ergebnis DIV ausfüllen). Warteschlange nach: [Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result] .
  • Veranstaltung: 4. Zeile im Handler ausführen (Status DIV mit "DONE" auffüllen). Warteschlange: [Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value] .
  • Ereignis: Ausführen impliziert return von onclick Handler sub. Wir nehmen den "Execute OnClick handler" aus der Warteschlange und beginnen mit der Ausführung des nächsten Elements in der Warteschlange.
  • HINWEIS: Da wir die Berechnung bereits abgeschlossen haben, sind für den Benutzer bereits 3 Minuten vergangen. Die Neuauslosung hat noch nicht stattgefunden!!!
  • Ereignis: Neuzeichnen des Status DIV mit dem Wert "Berechnen". Wir führen das Neuzeichnen durch und nehmen es aus der Warteschlange.
  • Ereignis: Neuzeichnen von Ergebnis DIV mit Ergebniswert. Wir führen das erneute Zeichnen durch und nehmen es aus der Warteschlange.
  • Ereignis: erneutes Zeichnen von Status DIV mit dem Wert "Done". Wir führen das Neuzeichnen durch und nehmen es aus der Warteschlange. Aufmerksame Betrachter könnten sogar bemerken, dass "Status DIV mit dem Wert "Calculating" für den Bruchteil einer Mikrosekunde blinkt - NACH ABSCHLUSS DER BERECHNUNG

Das zugrundeliegende Problem besteht also darin, dass das Ereignis zum erneuten Zeichnen von "Status" DIV am Ende in die Warteschlange gestellt wird, NACH dem Ereignis "Zeile 2 ausführen", das 3 Minuten dauert, so dass das tatsächliche erneute Zeichnen erst erfolgt, NACH dem die Berechnung abgeschlossen ist.


Zur Rettung kommt die setTimeout() . Wie kann das helfen? Weil durch den Aufruf von lang ausgeführtem Code über setTimeout erstellen Sie tatsächlich 2 Ereignisse: setTimeout Ausführung selbst und (aufgrund der Zeitüberschreitung von 0) einen separaten Warteschlangeneintrag für den auszuführenden Code.

Um das Problem zu beheben, ändern Sie also Ihre onClick Handler zu ZWEI Anweisungen (in einer neuen Funktion oder einfach einem Block innerhalb onClick ):

  1. Den Status "Berechnen... kann ~3 Minuten dauern" in den Status DIV einfügen

  2. Ausführen setTimeout() mit 0 Timeout und einem Aufruf von LongCalc() Funktion .

    LongCalc() Funktion ist fast die gleiche wie beim letzten Mal, hat aber offensichtlich nicht "Calculating..." Status DIV update als ersten Schritt; und startet stattdessen die Berechnung sofort.

Wie sehen nun die Ereignisfolge und die Warteschlange aus?

  • Warteschlange: [Empty]
  • Veranstaltung: Klicken Sie auf die Schaltfläche. Warteschlange nach Ereignis: [Execute OnClick handler(status update, setTimeout() call)]
  • Veranstaltung: Erste Zeile im OnClick-Handler ausführen (z.B. Status DIV-Wert ändern). Warteschlange nach Ereignis: [Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value] .
  • Veranstaltung: Zweite Zeile im Handler ausführen (setTimeout-Aufruf). Warteschlange nach: [re-draw Status DIV with "Calculating" value] . Die Warteschlange enthält seit 0 Sekunden keine neuen Einträge mehr.
  • Veranstaltung: Alarm vom Timeout geht los, 0 Sekunden später. Warteschlange nach: [re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)] .
  • Veranstaltung: Status-DIV mit dem Wert "Berechnen" neu zeichnen . Warteschlange nach: [execute LongCalc (lines 1-3)] . Bitte beachten Sie, dass dieses Ereignis auch VOR dem Auslösen des Alarms eintreten kann, was genauso gut funktioniert.
  • ...

Hurra! Der Status DIV wurde gerade auf "Calculating..." aktualisiert, bevor die Berechnung begann!!!



Im Folgenden finden Sie den Beispielcode aus dem JSFiddle, der diese Beispiele veranschaulicht: http://jsfiddle.net/C2YBE/31/ :

HTML-Code:

<table border=1>
    <tr><td><button id='do'>Do long calc - bad status!</button></td>
        <td><div id='status'>Not Calculating yet.</div></td>
    </tr>
    <tr><td><button id='do_ok'>Do long calc - good status!</button></td>
        <td><div id='status_ok'>Not Calculating yet.</div></td>
    </tr>
</table>

JavaScript-Code: (Ausgeführt am onDomReady und erfordert möglicherweise jQuery 1.9)

function long_running(status_div) {

    var result = 0;
    // Use 1000/700/300 limits in Chrome, 
    //    300/100/100 in IE8, 
    //    1000/500/200 in FireFox
    // I have no idea why identical runtimes fail on diff browsers.
    for (var i = 0; i < 1000; i++) {
        for (var j = 0; j < 700; j++) {
            for (var k = 0; k < 300; k++) {
                result = result + i + j + k;
            }
        }
    }
    $(status_div).text('calculation done');
}

// Assign events to buttons
$('#do').on('click', function () {
    $('#status').text('calculating....');
    long_running('#status');
});

$('#do_ok').on('click', function () {
    $('#status_ok').text('calculating....');
    // This works on IE8. Works in Chrome
    // Does NOT work in FireFox 25 with timeout =0 or =1
    // DOES work in FF if you change timeout from 0 to 500
    window.setTimeout(function (){ long_running('#status_ok') }, 0);
});

12 Stimmen

Tolle Antwort DVK! Hier ist eine Zusammenfassung, die Ihr Beispiel illustriert gist.github.com/kumikoda/5552511#file-timeout-html

4 Stimmen

Wirklich coole Antwort, DVK. Nur um einfach zu machen, um sich vorzustellen, habe ich diesen Code zu jsfiddle setzen jsfiddle.net/thangchung/LVAaV

4 Stimmen

@ThangChung - Ich habe versucht, eine bessere Version (2 Schaltflächen, eine für jeden Fall) in JSFiddle zu machen. Es funktioniert als eine Demo auf Chrome und IE, aber nicht auf FF aus irgendeinem Grund - siehe jsfiddle.net/C2YBE/31 . Ich habe gefragt, warum FF hier nicht funktioniert: stackoverflow.com/questions/20747591/

94voto

Andy Punkte 1229

Werfen Sie einen Blick auf John Resigs Artikel über Wie JavaScript-Timer funktionieren . Wenn Sie eine Zeitüberschreitung festlegen, wird der asynchrone Code in eine Warteschlange gestellt, bis die Engine den aktuellen Aufrufstapel ausführt.

29voto

Jose Basilio Punkte 49489

setTimeout() verschafft Ihnen eine gewisse Zeit, bis die DOM-Elemente geladen sind, auch wenn sie auf 0 gesetzt ist.

Sehen Sie sich das an: setTimeout

26voto

Arley Punkte 919

Browser verfügen über einen Prozess namens "Haupt-Thread", der für die Ausführung einiger JavaScript-Aufgaben zuständig ist, z. B. für UI-Aktualisierungen: Malen, Neuzeichnen, Reflow usw. JavaScript-Aufgaben werden in eine Warteschlange gestellt und dann an den Haupt-Thread des Browsers zur Ausführung weitergeleitet. Wenn UI-Aktualisierungen erzeugt werden, während der Haupt-Thread beschäftigt ist, werden Aufgaben in die Nachrichtenwarteschlange aufgenommen.

0 Stimmen

"Jede JavaScript-Ausführung und UI-Update-Aufgaben werden dem Browser-Ereignis-Warteschlangensystem hinzugefügt, dann werden diese Aufgaben an den Haupt-UI-Thread des Browsers weitergeleitet, um ausgeführt zu werden."....source please?

4 Stimmen

Hochleistungs-JavaScript (Nicholas Zakas, Stoyan Stefanov, Ross Harmes, Julien Lecomte, und Matt Sweeney)

0 Stimmen

Abwärtsstimme für dies add this fn to the end of the queue . Am wichtigsten ist, wo genau setTimeout fügt diese Funktion am Ende dieses Schleifenzyklus oder ganz am Anfang des nächsten Schleifenzyklus hinzu.

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