Pourquoi les promesses javascript ES6 continuent-elles à s'exécuter après une résolution?

97

D'après ce que je comprends, une promesse est quelque chose qui peut résoudre () ou rejeter (), mais j'ai été surpris de découvrir que le code de la promesse continue de s'exécuter après l'appel d'une résolution ou d'un rejet.

J'ai considéré que la résolution ou le rejet était une version asynchrone de exit ou de retour, qui arrêterait toute exécution immédiate des fonctions.

Quelqu'un peut-il expliquer pourquoi l'exemple suivant montre parfois le fichier console.log après un appel de résolution:

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
la source
12
Question raisonnable, mais là encore, JS exécute juste une instruction après l'autre comme vous le lui dites. resolve()n'est pas une instruction de contrôle JS qui aurait comme par magie l'effet return, c'est juste un appel de fonction, et oui, l'exécution continue après elle.
C'est une bonne question, et même après avoir lu toutes les réponses, je ne suis pas sûr des meilleures pratiques ...
Gabriel Glenn
Je pense que le malentendu vient de ce que vous terminez exactement avec résoudre (): la promesse EST résolue juste après que vous appelez resolver (), mais comme déjà dit par d'autres, cela ne signifie pas que la fonction qui a mis fin à la promesse avait mis fin à son devoir aussi, donc il continue jusqu'à ce qu'il atteigne une fin "normale".
Giuseppe Bertone

Réponses:

143

JavaScript a le concept de "courir jusqu'à la fin" . À moins qu'une erreur ne soit générée, une fonction est exécutée jusqu'à ce qu'une returninstruction ou sa fin soit atteinte. Un autre code en dehors de la fonction ne peut pas interférer avec cela (à moins que, encore une fois, une erreur ne soit générée).

Si vous souhaitez resolve()quitter votre fonction d'initialisation, vous devez l'ajouter en return:

return new Promise(function(resolve, reject) {
    return resolve();
    console.log("Not doing more stuff after a return statement");
});
Félix Kling
la source
Salut Felix - je pense que ce n'est qu'une partie de l'histoire - l'autre partie resolve()est elle-même une fonction asynchrone. Comme nous l'avons vu dans l'autre réponse (supprimée), certaines personnes pensent que l'appel resolveexécutera immédiatement tous les rappels.
Alnitak
3
@Alnitak resolvelui-même n'est pas asynchrone, il est complètement synchrone. Bien qu'en utilisant strictement l'API ES6, il n'est pas observable si elle est synchrone ou asynchrone.
Esailija
1
@Esailija ok, peut-être que je n'étais pas clair. Certaines personnes pensent que l'appel resolveentraînera l' appel immédiat de tous les rappels enregistrés, de sorte qu'ils font partie de la pile d'appels actuelle. Ce n'est pas vrai, au lieu de cela, il met simplement en file d'attente les rappels (et vous avez raison, ce n'est pas asynchrone, mais il fait juste son truc et se termine immédiatement)
Alnitak
@Alnitak: Je comprends ce que vous dites. Je l'ai simplement interprété comme la raison pour laquelle le console.logmessage apparaît au lieu de savoir pourquoi il apparaît dans cet ordre. Jusqu'à présent, ce qui resolvefait et comment les promesses n'a aucun rapport avec la manière dont j'interprète la question. Mais bien sûr, il est toujours important de savoir dans le contexte des promesses. Une des raisons pour lesquelles j'ai voté pour votre réponse :)
Felix Kling
9
@Bergi, dans votre édition, vous dites "return resolution ();" ce qui semble inhabituel. Afin de me convaincre qu'il n'y a rien d'important à faire là-bas, j'ai dû lire la documentation et voir que (1) résoudre () ne semble pas retourner quoi que ce soit de conséquence, et (2) la valeur de retour du rappel d'initialisation ne le fait pas semblent être utilisés. Ne serait-il pas plus clair de dire "résoudre (); return;" évitant ainsi cette distraction?
Don Hatch
19

Les rappels qui seront resolveappelés lors d' une promesse sont toujours requis par la spécification pour être appelés de manière asynchrone. Cela permet de garantir un comportement cohérent lors de l'utilisation de promesses pour un mélange d'actions synchrones et asynchrones.

Par conséquent, lorsque vous appelez, resolvele rappel est mis en file d'attente et l'exécution de la fonction se poursuit immédiatement avec tout code suivant l' resolve()appel.

Ce n'est qu'une fois que la boucle d'événement JS a retrouvé le contrôle que le rappel peut être supprimé de la file d'attente et réellement appelé.

Alnitak
la source
1
La mise en file d'attente de rappel est documentée dans A + Specs ou dans ES6?
thefourtheye
5
@thefourtheye: La spécification de la boucle d'événements fait maintenant partie de HTML5 . ES6 définit une méthode interne appelée EnqueueJob, qui est appelée par .then.
Felix Kling
@thefourtheye: En fait, ES6 semble également définir les files d'attente: people.mozilla.org/~jorendorff/… . Je suppose que la boucle d'événements est liée d'une manière ou d'une autre.
Felix Kling
@FelixKling merci pour les liens - je savais que c'était ainsi que cela fonctionnait, mais je ne pouvais pas citer le chapitre et le verset
Alnitak
2
@FelixKling c'est microtasks / macrotasks, voici la partie de la spécification qui "reporte" "Quand il n'y a pas de contexte d'exécution en cours et que la pile de contexte d'exécution est vide, l'implémentation ECMAScript supprime le premier PendingJob d'une file d'attente de travaux et utilise les informations contenues pour créer un contexte d'exécution et démarrer l'exécution de l'opération abstraite Job associée. "
Benjamin Gruenbaum