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:
-
Macht eine sehr lange Berechnung (z.B. 3 Minuten)
-
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:
-
Den Status "Berechnen... kann ~3 Minuten dauern" in den Status DIV einfügen
-
Macht eine sehr lange Berechnung (z.B. 3 Minuten)
-
Druckt die Ergebnisse der Berechnung in das Ergebnisfeld.
-
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
):
-
Den Status "Berechnen... kann ~3 Minuten dauern" in den Status DIV einfügen
-
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);
});
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.
8 Stimmen
setTimeout(fn)
ist identisch mitsetTimeout(fn, 0)
Übrigens.0 Stimmen
Relevant für diese Frage ist die Methode queueMicrotask() , die später eingeführt wurde.