web-dev-qa-db-fra.com

Pourquoi le constructeur Promise nécessite-t-il une fonction qui appelle «résoudre» une fois terminée, mais «alors» ne le fait pas - il renvoie une valeur à la place?

Alors que je plonge dans l'étude de Promises, ma compréhension s'est arrêtée sur la question suivante que je ne trouve pas discutée (tout ce que je trouve sont des discussions spécifiques sur le constructeur Promise et le Promise 'then' - mais pas une discussion qui compare leurs modèles de conception).


1. Le constructeur Promise

De la documentation MDN , nous avons cette utilisation du constructeur Promise (avec mon commentaire ajouté):

new Promise(function(resolve, reject) { ... }); // <-- Call this Stage 1

Objet de fonction avec deux arguments resolve et reject. Le premier argument remplit la promesse, le deuxième argument la rejette. Nous pouvons appeler ces fonctions, une fois notre opération terminée.


2. La fonction then

Passons à la fonction then qui peut être appelée sur un objet Promise (qui renvoie un nouveau Promise object), nous avons la signature de fonction suivante décrite par la documentation (avec mes commentaires ajoutés)::

p.then(onFulfilled, onRejected);

Chaînage

Étant donné que la méthode then renvoie une promesse, vous pouvez facilement chaîner puis appeler.

var p2 = new Promise(function(resolve, reject) {
  resolve(1); // <-- Stage 1 again
});

p2.then(function(value) {
  console.log(value); // 1
  return value + 1; // <-- Call this Stage 2
}).then(function(value) {
  console.log(value); // 2
});

Ma question

D'après l'extrait de code ci-dessus, il me semble clair que la valeur passée à la fonction resolve à l'étape 1 (dans la deuxième occurrence de resolve - sous (2), ci-dessus) est passé à l'étape suivante (la première fonction then qui suit dans le même extrait de code). Il n'y a pas de valeur de retour à l'étape 1. Cependant, c'est la valeur de retour à l'étape 2 qui est transmise à l'étape suivante après cela (la deuxième fonction then).

Ce manque de correspondance entre le modèle de conception pour la création d'un Promise et l'utilisation de la fonction then sur une promesse existante (qui renvoie également un Promise), juste un coup de chance historique (l'un nécessite d'appeler un rappel mais ne renvoie rien, et l'autre renvoie une valeur mais n'appelle pas de rappel)?

Ou manque-t-il une raison sous-jacente pour laquelle le constructeur Promise utilise un modèle de conception différent de la fonction then?

33
Dan Nissenbaum

réponse de Bergi est excellent et m'a été très utile. Cette réponse est complémentaire de la sienne. Afin de visualiser la relation entre le constructeur Promise() et la méthode then(), j'ai créé ce diagramme. J'espère que ça aide quelqu'un ... peut-être même moi, dans quelques mois.

L'idée principale ici est que la fonction "exécuteur" passée au constructeur Promise() met les tâches en mouvement qui définiront l'état de la promesse; tandis que les gestionnaires que vous passez à then() réagiront à l'état de la promesse.

Diagram: Promise() executor vs. then() method (Exemples de code adaptés de Tutoriel classique de Jake Archibald .)

Il s'agit d'une vue très simplifiée de la façon dont les choses fonctionnent, en omettant de nombreux détails importants. Mais je pense que si l'on peut garder une bonne vue d'ensemble du but recherché, cela aidera à éviter toute confusion lorsque l'on entrera dans les détails.

Quelques détails sélectionnés

L'exécuteur est appelé immédiatement

Un détail important est que la fonction d'exécuteur passée au constructeur Promise() est appelée immédiatement (avant que le constructeur ne retourne la promesse); tandis que les fonctions de gestionnaire passées à la méthode then() ne seront appelées que plus tard (si jamais).

Bergi l'a mentionné, mais je voulais le reformuler sans utiliser les termes a/de manière synchrone, ce qui peut être confondu si vous ne lisez pas attentivement: la distinction entre une fonction appel quelque chose de manière asynchrone vs. = être appelé de manière asynchrone est facile à ignorer dans la communication.

resolve() n'est pas onFulfill()

Un autre détail sur lequel je voudrais insister, car cela m'a dérouté pendant un certain temps, est que les rappels resolve() et reject() passés à la fonction d'exécuteur du constructeur Promise() ne sont pas les rappels passés ultérieurement à la méthode then(). Cela semble évident rétrospectivement, mais la connexion apparente m'a fait tourner en rond pendant trop longtemps. Il y a certainement une connexion, mais elle est lâche et dynamique.

Au lieu de cela, les rappels resolve() et reject() sont des fonctions fournies par le "système" , et sont passées à la fonction exécuteur par le constructeur Promise lorsque vous créez une promesse. Lorsque la fonction resolve() est appelée, un code système est exécuté qui modifie potentiellement l'état de la promesse et conduit éventuellement à un rappel onFulfilled() appelé de manière asynchrone. Ne pensez pas d'appeler resolve() comme un wrapper serré pour appeler onFulfill()!

26
LarsH

Il n'y a pas de correspondance entre le constructeur Promise et la méthode then car ce sont deux choses indépendantes, conçues à des fins différentes.

Le constructeur Promise n'est utilisé que pour promettant1 fonctions asynchrones. En effet, comme vous le dites, il est construit sur invocationresolve/reject rappels à envoi asynchrone valeurs, et là n'y a pas de valeur de retour dans ce cas.

Le fait que le constructeur Promise lui-même accepte ce rappel "résolveur" (auquel il passe de manière synchrone resolve et reject) est en fait une amélioration de l'ancien différé pattern, et ne présente aucune similitude avec les rappels then.

var p = new Promise(function(res, rej) {    |    var def = Promise.Deferred();
    setTimeout(res, 100);                   |    setTimeout(def.resolve, 100);
});                                         |    var p = def.promise;

Les rappels then en revanche sont des rappels asynchrones classiques, avec la fonctionnalité supplémentaire que vous pouvez return à partir d'eux. Ce sont des valeurs appelées de manière asynchrone à recevoir.

p.then(function(val) { … });

Pour résumer les différences:

  • Promise est un constructeur, tandis que then est une méthode
  • Promise prend un rappel, tandis que then prend jusqu'à deux
  • Promise appelle son rappel de manière synchrone, tandis que then appelle ses rappels de manière asynchrone
  • Promise invoque toujours son rappel,
    then peut ne pas appeler ses rappels (si la promesse n'est pas remplie/rejetée)
  • Promise transmet les capacités pour résoudre/rejeter une promesse de rappel,
    then transmet la valeur de résultat/motif de rejet de la promesse à laquelle il a été appelé
  • Promise invoque son rappel dans le but d'exécuter des effets secondaires (appelez reject/resolve),
    then invoque ses rappels pour leurs valeurs de résultat (pour le chaînage)

Oui, les deux renvoient des promesses, bien qu'ils partagent ce trait avec de nombreuses autres fonctions (Promise.resolve, Promise.reject, fetch,…). En fait, tous ces éléments sont basés sur la même construction de promesses et capacités de résolution/rejet que le constructeur Promise fournit également, bien que ce ne soit pas leur objectif principal. then offre essentiellement la possibilité d'attacher des rappels onFulfilled/onRejected à une promesse existante, qui est plutôt diamétrale pour le constructeur Promise.

Le fait que les deux utilisent les rappels n'est qu'une coïncidence - pas un hasard historique, mais plutôt la coadaptation d'une fonction linguistique.

1: Idéalement, vous n'en auriez jamais besoin car toutes les API nativement asynchrones renvoient des promesses

23
Bergi

L'intérêt de la fonction d'exécuteur du constructeur de promesses est de diffuser les fonctions de résolution et de rejet dans du code sans utilisation de promesses, de l'encapsuler et de le convertir pour utiliser une promesse. Si vous vouliez limiter cela aux fonctions synchrones uniquement, alors oui, une valeur de retour de la fonction aurait pu être utilisée à la place, mais cela aurait été idiot car la partie utile est de diffuser le résolveur et de rejeter les fonctions vers du code qui s'exécute effectivement plus tard. (bien après le retour), par exemple aux rappels passés à une API asynchrone.

6
jib

Inspiré par les réponses précédentes (je vais aborder la partie qui m'a le plus dérouté):

Les arguments resolve et reject dans le constructeur Promise ne sont pas des fonctions que vous définissez. Considérez-les comme des crochets que vous pouvez intégrer dans votre code d'opération asynchrone (généralement vous resolve avec une réponse réussie et reject avec une raison d'échec), de sorte que javascript ait un moyen de marquer éventuellement la promesse comme rempli ou rejeté selon le résultat de votre opération asynchrone; une fois que cela se produit, la fonction appropriée que vous avez définie dans then(fun1, fun2) est déclenchée pour consommer la promesse (soit fun1(success_response) ou fun2(failure_reason), selon que la promesse est remplie/rejetée ). Puisque fun1 et fun2 sont de simples anciennes fonctions javascript (elles prennent simplement le résultat futur de votre opération asynchrone comme arguments), elles return valeurs (qui peuvent être undefined si vous ne retournez pas explicitement) .

Consultez également d'excellents articles de Mozilla:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

4
Yibo Yang