Warum werden Javascript ES6 Promises nach einer Lösung weiterhin ausgeführt?

97

Soweit ich weiß, kann ein Versprechen aufgelöst () oder abgelehnt () werden, aber ich war überrascht, dass der Code im Versprechen weiterhin ausgeführt wird, nachdem eine Lösung oder Ablehnung aufgerufen wurde.

Ich hielt das Auflösen oder Ablehnen für eine asynchrone Version von exit oder return, die jede sofortige Funktionsausführung stoppen würde.

Kann jemand den Gedanken erklären, warum das folgende Beispiel manchmal die console.log nach einem Auflösungsaufruf zeigt:

var call = function() {
    return new Promise(function(resolve, reject) {
        resolve();
        console.log("Doing more stuff, should not be visible after a resolve!");
    });
};

call().then(function() {
    console.log("resolved");
});

jsbin

Ludwig van Beethoven
quelle
12
Vernünftige Frage, aber andererseits führt JS nur eine Anweisung nach der anderen aus, wie Sie es ihm sagen. resolve()ist keine JS-Steueranweisung, die sich auf magische Weise auswirken würde return, sondern nur ein Funktionsaufruf, und ja, die Ausführung wird danach fortgesetzt.
Dies ist eine gute Frage, und selbst nachdem ich alle Antworten gelesen habe, bin ich mir nicht sicher über die Best Practices ...
Gabriel Glenn
Ich denke, das Missverständnis rührt von dem her, was genau Sie mit Entschlossenheit () beenden: Das Versprechen wird unmittelbar nach dem Aufruf von Entschlossenheit () aufgelöst, aber wie bereits von anderen gesagt, bedeutet dies nicht, dass die Funktion, die das Versprechen beendet hat, es beendet hat Pflicht auch, so geht es weiter, bis es eine "normale" Kündigung erreicht.
Giuseppe Bertone

Antworten:

142

JavaScript hat das Konzept "Run to Completion" . Sofern kein Fehler ausgelöst wird, wird eine Funktion ausgeführt, bis eine returnAnweisung oder deren Ende erreicht ist. Anderer Code außerhalb der Funktion kann dies nicht stören (es sei denn, es wird erneut ein Fehler ausgegeben).

Wenn Sie resolve()Ihre Initialisierungsfunktion beenden möchten , müssen Sie Folgendes voranstellen return:

return new Promise(function(resolve, reject) {
    return resolve();
    console.log("Not doing more stuff after a return statement");
});
Felix Kling
quelle
Hallo Felix - ich denke, dass dies nur ein Teil der Geschichte ist - der andere Teil ist, dass resolve()es sich selbst um eine asynchrone Funktion handelt. Wie wir in der anderen (gelöschten) Antwort gesehen haben, glauben einige Leute, dass Anrufe resolvesofort zu Rückrufen führen.
Alnitak
3
@Alnitak resolveselbst ist nicht asynchron, es ist vollständig synchron. Obwohl ausschließlich die ES6-API verwendet wird, ist nicht erkennbar, ob sie synchron oder asynchron ist.
Esailija
1
@Esailija ok, vielleicht war ich unklar. Einige Leute glauben, dass Anrufe resolvedazu führen, dass registrierte Rückrufe sofort aufgerufen werden, sodass sie Teil des aktuellen Anrufstapels sind. Das ist nicht wahr, stattdessen werden nur die Rückrufe in die Warteschlange gestellt (und Sie haben Recht, es ist nicht asynchron, aber es macht einfach seine Sache und endet sofort)
Alnitak
@ Alnitak: Ich verstehe was du sagst. Ich habe es nur so interpretiert, warum die console.logShow bei statt statt warum in dieser Reihenfolge erscheint. Bisher resolveist es für die Interpretation der Frage unerheblich , was und wie Versprechen sind. Aber natürlich ist es immer noch wichtig, im Zusammenhang mit Versprechungen Bescheid zu wissen. Einer der Gründe, warum ich Ihre Antwort positiv bewertet habe :)
Felix Kling
9
@Bergi, in deiner Bearbeitung sagst du "return resolve ();" das scheint ungewöhnlich. Um mich davon zu überzeugen, dass dort nichts Wichtiges vor sich geht, musste ich die Dokumentation lesen und feststellen, dass (1) resolve () scheinbar keine Konsequenzen zurückgibt und (2) der Rückgabewert des Initialisierungsrückrufs nicht scheinen verwendet zu werden. Wäre es nicht klarer zu sagen "resolve (); return;" um diese Ablenkung zu vermeiden?
Don Hatch
19

Die Rückrufe, die aufgerufen werden, wenn Sie resolveein Versprechen abgeben, müssen von der Spezifikation weiterhin asynchron aufgerufen werden. Dies dient dazu, ein konsistentes Verhalten sicherzustellen, wenn Versprechen für eine Mischung aus synchronen und asynchronen Aktionen verwendet werden.

Wenn Sie resolveden Rückruf aufrufen, wird er daher in die Warteschlange gestellt und die Funktionsausführung wird sofort mit jedem Code fortgesetzt, der auf den resolve()Aufruf folgt .

Erst wenn die JS-Ereignisschleife wieder gesteuert wird, kann der Rückruf aus der Warteschlange entfernt und tatsächlich aufgerufen werden.

Alnitak
quelle
1
Die Rückrufwarteschlange ist in A + Specs oder in ES6? Dokumentiert.
thefourtheye
5
@thefourtheye: Die Ereignisschleifenspezifikation ist jetzt tatsächlich Teil von HTML5 . ES6 definiert eine interne Methode namens EnqueueJob, die von aufgerufen wird .then.
Felix Kling
@thefourtheye: Tatsächlich scheint ES6 auch Warteschlangen zu definieren: people.mozilla.org/~jorendorff/… . Ich denke, das hängt mit der Ereignisschleife auf die eine oder andere Weise zusammen.
Felix Kling
@ FelixKling danke für die Links - ich wusste, dass es so funktionierte, konnte aber Kapitel und Vers nicht zitieren
Alnitak
2
@FelixKling es ist Mikrotasks / Makrotasks, hier ist der Teil in der Spezifikation, der "verzögert" "Wenn kein Ausführungskontext ausgeführt wird und der Ausführungskontextstapel leer ist, entfernt die ECMAScript-Implementierung den ersten PendingJob aus einer Jobwarteschlange und verwendet die enthaltenen Informationen darin, um einen Ausführungskontext zu erstellen und die Ausführung der zugehörigen Job-Abstract-Operation zu starten. "
Benjamin Gruenbaum