Alors que je plonge dans l'étude de Promise
s, 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
etreject
. 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
?
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.
(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.
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()
!
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éthodePromise
prend un rappel, tandis que then
prend jusqu'à deuxPromise
appelle son rappel de manière synchrone, tandis que then
appelle ses rappels de manière asynchronePromise
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
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.
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