J'ai restructuré mon code en promesses et construit une merveilleuse chaîne de promesses longue et plate , composée de plusieurs .then()
rappels. En fin de compte, je veux retourner une valeur composite et j'ai besoin d'accéder à plusieurs résultats de promesse intermédiaires . Cependant, les valeurs de résolution du milieu de la séquence ne sont pas incluses dans le dernier rappel, comment y accéder?
function getExample() {
return promiseA(…).then(function(resultA) {
// Some processing
return promiseB(…);
}).then(function(resultB) {
// More processing
return // How do I gain access to resultA here?
});
}
javascript
, elle est pertinente dans une autre langue. J'utilise juste la réponse "casser la chaîne" en java et jdeferredRéponses:
Briser la chaîne
Lorsque vous devez accéder aux valeurs intermédiaires de votre chaîne, vous devez diviser votre chaîne en plusieurs morceaux dont vous avez besoin. Au lieu d'attacher un rappel et d'essayer d'utiliser son paramètre plusieurs fois, attachez plusieurs rappels à la même promesse - partout où vous avez besoin de la valeur de résultat. N'oubliez pas, une promesse ne représente (des procurations) qu'une valeur future ! À côté de dériver une promesse de l'autre dans une chaîne linéaire, utilisez les combinateurs de promesses qui vous sont fournis par votre bibliothèque pour construire la valeur du résultat.
Cela se traduira par un flux de contrôle très simple, une composition claire des fonctionnalités et donc une modularisation facile.
Au lieu du paramètre destructuration dans le rappel après
Promise.all
que seul est devenu disponible avec ES6, en ES5 l'then
appel serait remplacé par une méthode d'assistance qui a été fourni nifty par de nombreuses bibliothèques de promesse ( Q , Bluebird , quand , ...):.spread(function(resultA, resultB) { …
.Bluebird propose également une
join
fonction dédiée pour remplacer cette combinaisonPromise.all
+spread
par une construction plus simple (et plus efficace):la source
promiseA
etpromiseB
sont les fonctions (de retour de promesse) ici.spread
était super utile dans ce modèle. Pour des solutions plus modernes, voir la réponse acceptée. Cependant, j'ai déjà mis à jour la réponse explicite-passthrough , et il n'y a vraiment aucune bonne raison de ne pas mettre à jour celle-ci également.ECMAScript Harmony
Bien sûr, ce problème a également été reconnu par les concepteurs de langage. Ils ont fait beaucoup de travail et la proposition de fonctions asynchrones est finalement devenue
ECMAScript 8
Vous n'avez plus besoin d'une seule fonction d'
then
appel ou de rappel, car dans une fonction asynchrone (qui renvoie une promesse lors de son appel), vous pouvez simplement attendre que les promesses soient résolues directement. Il comporte également des structures de contrôle arbitraires comme les conditions, les boucles et les clauses try-catch, mais pour des raisons de commodité, nous n'en avons pas besoin ici:ECMAScript 6
Pendant que nous attendions ES8, nous utilisions déjà un type de syntaxe très similaire. ES6 est venu avec des fonctions de générateur , qui permettent de séparer l'exécution en morceaux au niveau de
yield
mots-clés placés arbitrairement . Ces tranches peuvent être exécutées les unes après les autres, indépendamment, même de manière asynchrone - et c'est exactement ce que nous faisons lorsque nous voulons attendre une résolution de promesse avant d'exécuter l'étape suivante.Il existe des bibliothèques dédiées (comme co ou task.js ), mais aussi de nombreuses bibliothèques de promesses ont des fonctions d'assistance ( Q , Bluebird , when ,…) qui font cette exécution asynchrone étape par étape pour vous lorsque vous leur donnez une fonction de générateur qui donne des promesses.
Cela fonctionnait dans Node.js depuis la version 4.0, également quelques navigateurs (ou leurs éditions de développement) prenaient en charge la syntaxe du générateur relativement tôt.
ECMAScript 5
Cependant, si vous voulez / devez être rétrocompatible, vous ne pouvez pas utiliser ceux sans transpilateur. Les fonctions de générateur et les fonctions asynchrones sont prises en charge par l'outillage actuel, voir par exemple la documentation de Babel sur les générateurs et les fonctions asynchrones .
Et puis, il existe également de nombreux autres langages de compilation vers JS dédiés à faciliter la programmation asynchrone. Ils utilisent généralement une syntaxe similaire à
await
(par exemple Iced CoffeeScript ), mais il y a aussi d' autres qui disposent d' une Haskell commedo
-notation (par exemple LatteJs , monadique , PureScript ou LispyScript ).la source
getExample
c'est toujours une fonction qui renvoie une promesse, fonctionnant exactement comme les fonctions des autres réponses, mais avec une syntaxe plus agréable. Vous pourriezawait
un appel dans une autreasync
fonction, ou vous pourriez enchaîner.then()
à son résultat.steps.next().value.then(steps.next)...
ça mais ça n'a pas marché.Inspection synchrone
Affecter des promesses pour des valeurs nécessaires ultérieurement à des variables, puis obtenir leur valeur via une inspection synchrone. L'exemple utilise la
.value()
méthode de bluebird mais de nombreuses bibliothèques proposent une méthode similaire.Cela peut être utilisé pour autant de valeurs que vous le souhaitez:
la source
Fermetures d'emboîtement (et)
L'utilisation de fermetures pour maintenir la portée des variables (dans notre cas, les paramètres de la fonction de rappel de succès) est la solution JavaScript naturelle. Avec des promesses, nous pouvons arbitrairement imbriquer et aplatir les
.then()
rappels - ils sont sémantiquement équivalents, à l'exception de la portée de l'interne.Bien sûr, cela construit une pyramide d'indentation. Si l'indentation devient trop importante, vous pouvez toujours appliquer les anciens outils pour contrer la pyramide du destin : modularisez, utilisez des fonctions nommées supplémentaires et aplatissez la chaîne de promesses dès que vous n'avez plus besoin d'une variable.
En théorie, vous pouvez toujours éviter plus de deux niveaux d'imbrication (en rendant explicites toutes les fermetures), en pratique, utilisez-en autant qu'il est raisonnable.
Vous pouvez également utiliser des fonctions d' assistance pour ce genre d' application partielle , comme
_.partial
de Souligné / lodash ou la native.bind()
méthode , pour plus indentation diminution:la source
bind
fonction des Monades. Haskell fournit du sucre syntaxique (do-notation) pour le faire ressembler à une syntaxe asynchrone / wait.Pass-through explicite
Semblable à l'imbrication des rappels, cette technique repose sur des fermetures. Pourtant, la chaîne reste plate - au lieu de ne transmettre que le dernier résultat, un objet d'état est transmis à chaque étape. Ces objets d'état accumulent les résultats des actions précédentes, transmettant à nouveau toutes les valeurs qui seront nécessaires plus tard, ainsi que le résultat de la tâche en cours.
Ici, cette petite flèche
b => [resultA, b]
est la fonction qui se fermeresultA
et passe un tableau des deux résultats à l'étape suivante. Qui utilise la syntaxe de déstructuration des paramètres pour le diviser à nouveau en variables uniques.Avant que la déstructuration ne soit disponible avec ES6, une méthode astucieuse d'aide appelée
.spread()
était fournie par de nombreuses bibliothèques de promesses ( Q , Bluebird , quand ,…). Il prend une fonction avec plusieurs paramètres - un pour chaque élément du tableau - à utiliser comme.spread(function(resultA, resultB) { …
.Bien sûr, cette fermeture nécessaire ici peut être encore simplifiée par certaines fonctions d'aide, par exemple
Alternativement, vous pouvez utiliser
Promise.all
pour produire la promesse du tableau:Et vous pouvez non seulement utiliser des tableaux, mais aussi des objets arbitrairement complexes. Par exemple, avec
_.extend
ouObject.assign
dans une fonction d'assistance différente:Bien que ce motif garantisse une chaîne plate et que les objets d'état explicites puissent améliorer la clarté, il deviendra fastidieux pour une longue chaîne. Surtout lorsque vous n'avez besoin que de l'état de façon sporadique, vous devez toujours le passer à chaque étape. Avec cette interface fixe, les rappels uniques dans la chaîne sont assez étroitement couplés et inflexibles à changer. Cela rend l'affacturage des étapes simples plus difficile, et les rappels ne peuvent pas être fournis directement à partir d'autres modules - ils doivent toujours être enveloppés dans du code passe-partout qui se soucie de l'état. Les fonctions d'aide abstraite comme celles ci-dessus peuvent atténuer un peu la douleur, mais elles seront toujours présentes.
la source
Promise.all
devrait être encouragée (cela ne fonctionnera pas dans ES6 lorsque la déstructuration le remplacera et le basculement de a.spread
versthen
donnera aux gens des résultats souvent inattendus. En ce qui concerne l'augmentation - je ne sais pas pourquoi vous avez besoin utiliser l'augmentation - l'ajout de choses au prototype de promesse n'est pas un moyen acceptable d'étendre de toute façon les promesses ES6 qui sont censées être étendues avec (le sous-classement actuellement non pris en charge)Promise.all
"? Aucune des méthodes de cette réponse ne rompra avec ES6. Passer d'unspread
à une déstructurationthen
ne devrait pas non plus poser de problème. Re .prototype.augment: Je savais que quelqu'un le remarquerait, j'aimais juste explorer les possibilités - aller le modifier.return [x,y]; }).spread(...
au lieu dereturn Promise.all([x, y]); }).spread(...
cela ne changerait pas lors de l'échange de propagation pour le sucre déstructurant es6 et ne serait pas non plus un cas étrange où les promesses traitent le retour des tableaux différemment de tout le reste.État contextuel mutable
La solution triviale (mais inélégante et plutôt sujette aux erreurs) consiste à simplement utiliser des variables de portée supérieure (auxquelles tous les rappels de la chaîne ont accès) et à leur écrire des valeurs de résultat lorsque vous les obtenez:
Au lieu de nombreuses variables, on peut également utiliser un objet (initialement vide), sur lequel les résultats sont stockés en tant que propriétés créées dynamiquement.
Cette solution présente plusieurs inconvénients:
La bibliothèque Bluebird encourage l'utilisation d'un objet transmis, en utilisant sa
bind()
méthode pour affecter un objet contextuel à une chaîne de promesses. Il sera accessible à partir de chaque fonction de rappel via lethis
mot-clé autrement inutilisable . Alors que les propriétés des objets sont plus sujettes aux fautes de frappe non détectées que les variables, le modèle est assez intelligent:Cette approche peut être facilement simulée dans des bibliothèques de promesses qui ne prennent pas en charge .bind (bien que d'une manière un peu plus verbeuse et ne puissent pas être utilisées dans une expression):
la source
.bind()
n'est pas nécessaire pour éviter les fuites de mémoireUn tour moins dur sur "l'état contextuel mutable"
L'utilisation d'un objet de portée locale pour collecter les résultats intermédiaires dans une chaîne de promesses est une approche raisonnable de la question que vous avez posée. Considérez l'extrait de code suivant:
la source
Promise
anti-modèle constructeur !Le nœud 7.4 prend désormais en charge les appels asynchrones / en attente avec l'indicateur d'harmonie.
Essaye ça:
et exécutez le fichier avec:
node --harmony-async-await getExample.js
Aussi simple que cela puisse être!
la source
Ces jours-ci, j'ai aussi rencontré des questions comme vous. Enfin, je trouve une bonne solution avec la question, c'est simple et bon à lire. J'espère que cela pourra vous aider.
Selon les promesses javascript de l'enchaînement
ok, regardons le code:
la source
.then
, mais le résultat d'avant. Par exemple,thirdPromise
accéder au résultat defirstPromise
.Une autre réponse, en utilisant la
babel-node
version <6En utilisant
async - await
npm install -g [email protected]
example.js:
Ensuite, courez
babel-node example.js
et le tour est joué!la source
Je ne vais pas utiliser ce modèle dans mon propre code car je ne suis pas un grand fan de l'utilisation de variables globales. Cependant, à la rigueur, cela fonctionnera.
L'utilisateur est un modèle Mongoose promis.
la source
globalVar
du tout, il suffit de faireUser.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });
?Une autre réponse, en utilisant l'exécuteur séquentiel nsynjs :
Mise à jour: exemple de travail ajouté
la source
Lorsque vous utilisez bluebird, vous pouvez utiliser la
.bind
méthode pour partager des variables dans la chaîne de promesses:veuillez consulter ce lien pour plus d'informations:
http://bluebirdjs.com/docs/api/promise.bind.html
la source
moyen facile: D
la source
Je pense que vous pouvez utiliser le hachage de RSVP.
Quelque chose comme ci-dessous:
la source
Promise.all
solution , uniquement avec un objet au lieu d'un tableau.Solution:
Vous pouvez mettre des valeurs intermédiaires dans la portée dans n'importe quelle fonction 'then' plus tard explicitement, en utilisant 'bind'. C'est une bonne solution qui ne nécessite pas de changer le fonctionnement des Promesses, et ne nécessite qu'une ou deux lignes de code pour propager les valeurs tout comme les erreurs sont déjà propagées.
Voici un exemple complet:
Cette solution peut être invoquée comme suit:
(Remarque: une version plus complexe et complète de cette solution a été testée, mais pas cet exemple de version, elle pourrait donc avoir un bogue.)
la source
async
/await
signifie toujours utiliser des promesses. Ce que vous pourriez abandonner, ce sont lesthen
appels avec rappels.Ce que j'apprends sur les promesses, c'est de l'utiliser uniquement car les valeurs de retour évitent de les référencer si possible. La syntaxe async / wait est particulièrement pratique pour cela. Aujourd'hui, tous les derniers navigateurs et nœuds le prennent en charge: https://caniuse.com/#feat=async-functions , est un comportement simple et le code ressemble à la lecture de code synchrone, oubliez les rappels ...
Dans les cas où je dois faire référence à une promesse, c'est lorsque la création et la résolution ont lieu à des endroits indépendants / non liés. Donc, au lieu d'une association artificielle et probablement d'un écouteur d'événements juste pour résoudre la promesse "distante", je préfère exposer la promesse en tant que différé, que le code suivant implémente dans un es5 valide
transpilé forme un de mes projets dactylographiés:
https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31
Pour les cas plus complexes, j'utilise souvent ces petits utilitaires de promesse sans dépendances testées et typées. p-map a été utile à plusieurs reprises. Je pense qu'il a couvert la plupart des cas d'utilisation:
https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=
la source