Beim Versuch, die Ausführungsreihenfolge von ES6-Versprechungen zu verstehen, ist mir aufgefallen, dass die Reihenfolge der Ausführung von verketteten Handlern davon abhängt, ob der vorherige Handler einen Wert oder ein Versprechen zurückgegeben hat.
Beispiel
Wird ausgegeben, wenn Sie direkt in der Chrome-Konsole (v 61) ausgeführt wird:
B
A
Wenn Sie jedoch auf die Schaltfläche Run code snippet
klicken, erhalte ich stattdessen den Befehl A
B
.
Ist die Ausführungsreihenfolge in ES6 für das obige Beispiel definiert oder liegt es an der Implementierung?
Wenn es definiert ist, was sollte die richtige Ausgabe sein?
Promise.resolve
wird angegeben, um ein aufgelöstes Versprechen zurückzugeben (überwältigend, richtig? 25.4.4.5, 25.4.1.5, 25.4.1.3.). a.then()
reiht daher einen Job sofort ( 25.4.5.3.1 , Schritt 8) jedes Mal ein. .then()
gibt niemals ein erfülltes Versprechen gemäß dieser Spezifikation zurück (für etwas Interessantes versuchst du Promise.resolve().then()
in deiner Chrome-Konsole¹).
Sagen wir das Versprechen a.then(v => Promise.resolve("A"))
und einen Teil seines zugehörigen Spezifikationsstatus p1 ². Dieser .then()
reiht einen Job ein, der aufgerufen werden soll ( 25.4.2.1 ) a.then(v => Promise.resolve("A"))
wie oben angegeben.
Der erste .then(v => console.log(v))
hängt eine Promise-Reaktion entsprechend v => console.log(v)
₁ an die Liste der erfüllten Reaktionen des ausstehenden Versprechens p1 an (immer noch 25.4.5.3.1 ).
Die Warteschlange ist jetzt:
v => Promise.resolve("A")
p1 hat nun v => console.log(v)
₁ in seiner Liste der Reaktionen auf die Erfüllung
Das Versprechen a.then(v => "B")
kann p2 sein. Das funktioniert jetzt genauso.
Die Warteschlange ist jetzt:
v => Promise.resolve("A")
v => "B"
p1 hat v => console.log(v)
₁ in seiner Liste der Reaktionen auf die Erfüllung
v => console.log(v)
₂ in seiner Liste der erfüllten Reaktionen Wir haben das Ende des Skripts erreicht.
Wenn der erste Job, der v => Promise.resolve("A")
entspricht, aus der Warteschlange genommen und aufgerufen wird (erneut 25.4.2.1 ) ), a then
wird im Ergebnis gefunden (dies ist der wichtige Teil), wodurch ein anderer Job in die Warteschlange eingereiht wird ( 25.4.1.3.2 , Schritt 12), unabhängig vom Promise-Status dieses Ergebnisses.
Die Warteschlange ist jetzt:
v => "B"
Promise.resolve("A").then
mit p1 auf [[Auflösen]] und [[Ablehnen]] p1 hat v => console.log(v)
₁ in seiner Liste der Reaktionen auf die Erfüllung
v => console.log(v)
₂ in seiner Liste der erfüllten Reaktionen Der nächste Job wird aus der Warteschlange genommen und aufgerufen. Ein aufrufbares then
ist nicht im Ergebnis gefunden, daher ist p2 sofort erfüllt ( 25.4.1.3.2 erneut, Schritt 11a) und reiht einen Job für jede von p2 -Folgen ein.
Die Warteschlange ist jetzt wie folgt:
Promise.resolve("A").then
mit p1 auf [[Auflösen]] und [[Ablehnen]] v => console.log(v)
₂ p1 hat v => console.log(v)
₁ in seiner Liste der Reaktionen auf die Erfüllung
Ich werde hier auf dieser Erklärungsebene aufhören, da Promise.resolve("A").then
die gesamte then
Sequenz erneut startet. Sie können jedoch sehen, wohin dies geht: Die Jobwarteschlange ist eine Warteschlange und eine Funktion, die die Ausgabe erzeugt, befindet sich in der Warteschlange und die andere wurde noch nicht hinzugefügt. Derjenige, der sich in der Warteschlange befindet, wird zuerst ausgeführt.
Die korrekte Ausgabe ist B, gefolgt von A.
Also, warum ist die Antwort in Chrome in einer eigenen Seite falsch? Es ist nicht ein Stack Overflow Snippets Shim; Sie können es mit ein wenig HTML allein oder in Node reproduzieren. Meine Vermutung ist, dass es eine Spec-Breaking-Optimierung ist.
Alternative Definitionen von thenable
mit diesem fun node --allow_natives_syntax
script!
¹ Für die Nachwelt: Es ist ein gelöstes Versprechen in Chrome 61.0.3163.100.
² Das ist weniger spezifisch als die Spezifikation, aber dies ist eine Antwort versucht, die Spezifikation zu beschreiben und nicht eine Spezifikation. Mit etwas Glück ist es sogar richtig.
Nach der ES-Spezifikation und deren Warteschlangensemantik sollte die Reihenfolge B
A
lauten, da das zusätzliche Versprechen in der ersten Kette eine zusätzliche Mikrotaskrunde erfordert. Dies verhindert jedoch die üblichen Optimierungen, z. B. die gleichzeitige Überprüfung des Auflösungsstatus und des Erfüllungswerts für bekannte Versprechen, anstatt ineffiziente Callbacks zu erstellen und jedes Mal then
zu durchlaufen, wie von der Spezifikation gefordert.
In jedem Fall sollten Sie keinen solchen Code schreiben oder sich nicht auf seine Reihenfolge verlassen . Sie haben zwei unabhängige Versprechungsketten - a.then(v => Promise.resolve("A"))
und a.then(v => "B")
, und wenn jeder von diesen auflösen wird, hängt davon ab, was ihre Rückrufe tun, was im Idealfall etwas asynchron mit unbekannter Auflösung ist. Die Promises / A + -Spezifikation lässt dies offen für Implementierungen, die mit synchronen Callback-Funktionen arbeiten. Die goldene Regel der asynchronen Programmierung im Allgemeinen und mit Versprechungen speziell ist es, immer explizit über die Reihenfolge zu sein, wenn (und nur wenn) Ihnen die Reihenfolge wichtig ist:
Tags und Links javascript ecmascript-6 es6-promise