Comment accéder aux résultats de promesse précédents dans une chaîne .then ()?

650

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?
    });
}
Bergi
la source
2
Cette question est vraiment intéressante et même si elle est balisée javascript, elle est pertinente dans une autre langue. J'utilise juste la réponse "casser la chaîne" en java et jdeferred
gontard

Réponses:

377

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.

function getExample() {
    var a = promiseA(…);
    var b = a.then(function(resultA) {
        // some processing
        return promiseB(…);
    });
    return Promise.all([a, b]).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Au lieu du paramètre destructuration dans le rappel après Promise.allque seul est devenu disponible avec ES6, en ES5 l' thenappel 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 joinfonction dédiée pour remplacer cette combinaison Promise.all+ spreadpar une construction plus simple (et plus efficace):


return Promise.join(a, b, function(resultA, resultB) {  });
Bergi
la source
1
Les fonctions à l'intérieur du tableau sont-elles exécutées dans l'ordre?
scaryguy
6
@scaryguy: Il n'y a aucune fonction dans le tableau, ce sont des promesses. promiseAet promiseBsont les fonctions (de retour de promesse) ici.
Bergi
2
@Roland n'a jamais dit que c'était :-) Cette réponse a été écrite à l'ère ES5 où aucune promesse n'était dans la norme, et 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.
Bergi
1
@reify Non, vous ne devriez pas faire ça , cela créerait des problèmes de rejet.
Bergi
1
Je ne comprends pas cet exemple. S'il existe une chaîne d'instructions «alors» qui nécessitent que les valeurs soient propagées tout au long de la chaîne, je ne vois pas comment cela résout le problème. Une promesse qui nécessite une valeur précédente NE PEUT PAS être déclenchée (créée) tant que cette valeur n'est pas présente. De plus, Promise.all () attend simplement que toutes les promesses de sa liste se terminent: il n'impose pas de commande. J'ai donc besoin que chaque fonction «suivante» ait accès à toutes les valeurs précédentes et je ne vois pas comment votre exemple fait cela. Vous devriez nous expliquer votre exemple, car je ne le crois pas ou ne le comprends pas.
David Spector
238

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' thenappel 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:

async function getExample() {
    var resultA = await promiseA(…);
    // some processing
    var resultB = await promiseB(…);
    // more processing
    return // something using both resultA and resultB
}

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 yieldmots-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.

var getExample = Promise.coroutine(function* () {
//               ^^^^^^^^^^^^^^^^^ Bluebird syntax
    var resultA = yield promiseA(…);
    // some processing
    var resultB = yield promiseB(…);
    // more processing
    return // something using both resultA and resultB
});

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 comme do-notation (par exemple LatteJs , monadique , PureScript ou LispyScript ).

Bergi
la source
@Bergi avez-vous besoin d'attendre la fonction asynchrone getExample () du code extérieur?
arisalexis
@arisalexis: Oui, getExamplec'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 pourriez awaitun appel dans une autre asyncfonction, ou vous pourriez enchaîner .then()à son résultat.
Bergi
1
Je suis curieux, pourquoi avez-vous répondu à votre propre question immédiatement après l'avoir posée? Il y a une bonne discussion ici, mais je suis curieux. Peut-être avez-vous trouvé vos réponses par vous-même après avoir demandé?
granmoe
@granmoe: J'ai posté toute la discussion à dessein en tant que cible canonique en double
Bergi
Existe-t-il un moyen (pas trop laborieux) d'éviter d'utiliser Promise.coroutine (c'est-à-dire n'utilisant pas Bluebird ou une autre bibliothèque, mais uniquement JS) dans l'exemple ECMAScript 6 avec la fonction générateur? J'avais en tête quelque chose comme steps.next().value.then(steps.next)...ça mais ça n'a pas marché.
un apprenant n'a pas de nom
102

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.

function getExample() {
    var a = promiseA(…);

    return a.then(function() {
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // a is guaranteed to be fulfilled here so we can just retrieve its
        // value synchronously
        var aValue = a.value();
    });
}

Cela peut être utilisé pour autant de valeurs que vous le souhaitez:

function getExample() {
    var a = promiseA(…);

    var b = a.then(function() {
        return promiseB(…)
    });

    var c = b.then(function() {
        return promiseC(…);
    });

    var d = c.then(function() {
        return promiseD(…);
    });

    return d.then(function() {
        return a.value() + b.value() + c.value() + d.value();
    });
}
Esailija
la source
6
Ceci est ma réponse préférée: dépendance lisible, extensible et minimale sur les fonctionnalités de bibliothèque ou de langage
Jason
13
@Jason: Euh, " une dépendance minimale aux fonctionnalités de la bibliothèque "? L'inspection synchrone est une fonctionnalité de bibliothèque et une fonctionnalité assez non standard pour démarrer.
Bergi
2
Je pense qu'il voulait dire des fonctionnalités spécifiques à la bibliothèque
deathgaze
54

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.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(function(resultB) {
            // more processing
            return // something using both resultA and resultB;
        });
    });
}

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.

function getExample() {
    // preprocessing
    return promiseA(…).then(makeAhandler(…));
}
function makeAhandler(…)
    return function(resultA) {
        // some processing
        return promiseB(…).then(makeBhandler(resultA, …));
    };
}
function makeBhandler(resultA, …) {
    return function(resultB) {
        // more processing
        return // anything that uses the variables in scope
    };
}

Vous pouvez également utiliser des fonctions d' assistance pour ce genre d' application partielle , comme _.partialde Souligné / lodash ou la native .bind()méthode , pour plus indentation diminution:

function getExample() {
    // preprocessing
    return promiseA(…).then(handlerA);
}
function handlerA(resultA) {
    // some processing
    return promiseB(…).then(handlerB.bind(null, resultA));
}
function handlerB(resultA, resultB) {
    // more processing
    return // anything that uses resultA and resultB
}
Bergi
la source
5
Cette même suggestion est donnée comme solution à «l'erreur avancée n ° 4» dans l'article de Nolan Lawson sur les promesses pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html . C'est une bonne lecture.
Robert
2
C'est exactement la bindfonction des Monades. Haskell fournit du sucre syntaxique (do-notation) pour le faire ressembler à une syntaxe asynchrone / wait.
zeronone
50

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.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(b => [resultA, b]); // function(b) { return [resultA, b] }
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Ici, cette petite flèche b => [resultA, b]est la fonction qui se ferme resultAet 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

function addTo(x) {
    // imagine complex `arguments` fiddling or anything that helps usability
    // but you get the idea with this simple one:
    return res => [x, res];
}


return promiseB(…).then(addTo(resultA));

Alternativement, vous pouvez utiliser Promise.allpour produire la promesse du tableau:

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return Promise.all([resultA, promiseB(…)]); // resultA will implicitly be wrapped
                                                    // as if passed to Promise.resolve()
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Et vous pouvez non seulement utiliser des tableaux, mais aussi des objets arbitrairement complexes. Par exemple, avec _.extendou Object.assigndans une fonction d'assistance différente:

function augment(obj, name) {
    return function (res) { var r = Object.assign({}, obj); r[name] = res; return r; };
}

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(augment({resultA}, "resultB"));
    }).then(function(obj) {
        // more processing
        return // something using both obj.resultA and obj.resultB
    });
}

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.

Bergi
la source
Tout d'abord, je ne pense pas que la syntaxe omettant le Promise.alldevrait être encouragée (cela ne fonctionnera pas dans ES6 lorsque la déstructuration le remplacera et le basculement de a .spreadvers thendonnera 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)
Benjamin Gruenbaum
@BenjaminGruenbaum: Qu'entendez-vous par " omission de la syntaxePromise.all "? Aucune des méthodes de cette réponse ne rompra avec ES6. Passer d'un spreadà une déstructuration thenne 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.
Bergi
Par la syntaxe du tableau, je veux dire return [x,y]; }).spread(...au lieu de return 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.
Benjamin Gruenbaum
3
C'est probablement la meilleure réponse. Les promesses sont la "programmation réactive fonctionnelle", et c'est souvent la solution employée. Par exemple, BaconJs, a #combineTemplate qui vous permet de combiner les résultats dans un objet qui est transmis le long de la chaîne
U Avalos
1
@CapiEtheriel La réponse a été écrite lorsque ES6 n'était pas aussi répandu qu'aujourd'hui. Ouais, peut-être qu'il est temps d'échanger les exemples
Bergi
35

É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:

function getExample() {
    var resultA;
    return promiseA(…).then(function(_resultA) {
        resultA = _resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both resultA and resultB
    });
}

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:

  • L'état mutable est moche et les variables globales sont mauvaises .
  • Ce modèle ne fonctionne pas au-delà des limites des fonctions, la modularisation des fonctions est plus difficile car leurs déclarations ne doivent pas quitter la portée partagée
  • La portée des variables n'empêche pas d'y accéder avant leur initialisation. Cela est particulièrement probable pour les constructions de promesses complexes (boucles, branchements, excuses) où des conditions de concurrence peuvent se produire. Passer explicitement l'état, une conception déclarative qui promet d'encourager, force un style de codage plus propre qui peut empêcher cela.
  • Il faut choisir correctement la portée de ces variables partagées. Il doit être local à la fonction exécutée pour empêcher les conditions de concurrence entre plusieurs appels parallèles, comme ce serait le cas si, par exemple, l'état était stocké sur une instance.

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 le thismot-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:

function getExample() {
    return promiseA(…)
    .bind({}) // Bluebird only!
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }).bind(); // don't forget to unbind the object if you don't want the
               // caller to access it
}

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):

function getExample() {
    var ctx = {};
    return promiseA(…)
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }.bind(ctx)).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }.bind(ctx));
}
Bergi
la source
.bind()n'est pas nécessaire pour éviter les fuites de mémoire
Esailija
@Esailija: Mais la promesse retournée ne contient-elle pas une référence à l'objet contextuel autrement? OK, bien sûr, le ramasse-miettes le traitera plus tard; ce n'est pas une «fuite» à moins que la promesse ne soit jamais tenue.
Bergi
Oui, mais les promesses contiennent également une référence à leurs valeurs de réalisation et à leurs raisons d'erreur ... mais rien ne contient de référence à la promesse, donc cela n'a pas d'importance
Esailija
4
Veuillez diviser cette réponse en deux, car j'ai presque voté sur le préambule! Je pense que "la solution triviale (mais inélégante et plutôt sujette aux erreurs)" est la solution la plus propre et la plus simple, car elle ne repose pas plus sur les fermetures et l'état mutable que votre réponse personnelle acceptée, mais est plus simple. Les fermetures ne sont ni mondiales ni mauvaises. Les arguments avancés contre cette approche n'ont aucun sens pour moi étant donné la prémisse. Quels problèmes de modularisation peut-on donner à une "merveilleuse longue chaîne de promesses plates"?
foc
2
Comme je l'ai dit ci-dessus, les promesses sont une "programmation réactive fonctionnelle". Ceci est un anti-modèle en FRP
U Avalos
15

Un 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:

function getExample(){
    //locally scoped
    const results = {};
    return promiseA(paramsA).then(function(resultA){
        results.a = resultA;
        return promiseB(paramsB);
    }).then(function(resultB){
        results.b = resultB;
        return promiseC(paramsC);
    }).then(function(resultC){
        //Resolve with composite of all promises
        return Promise.resolve(results.a + results.b + resultC);
    }).catch(function(error){
        return Promise.reject(error);
    });
}
  • Les variables globales sont mauvaises, donc cette solution utilise une variable de portée locale qui ne cause aucun dommage. Il n'est accessible qu'au sein de la fonction.
  • L'état mutable est moche, mais cela ne mute pas l'état de manière moche. L'état laid mutable se réfère traditionnellement à la modification des arguments d'état de fonction ou des variables globales, mais cette approche modifie simplement l'état d'une variable de portée locale qui existe dans le seul but d'agréger les résultats de la promesse ... une variable qui mourra d'une mort simple une fois la promesse résolue.
  • Les promesses intermédiaires ne sont pas empêchées d'accéder à l'état de l'objet de résultats, mais cela n'introduit pas de scénario effrayant où l'une des promesses de la chaîne ira mal et sabotera vos résultats. La responsabilité de définir les valeurs à chaque étape de la promesse est limitée à cette fonction et le résultat global sera correct ou incorrect ... ce ne sera pas un bug qui se reproduira des années plus tard en production (sauf si vous le souhaitez !)
  • Cela n'introduit pas de scénario de condition de concurrence qui résulterait de l'invocation parallèle, car une nouvelle instance de la variable de résultats est créée pour chaque appel de la fonction getExample.
Geai
la source
1
Évitez au moins l' Promiseanti-modèle constructeur !
Bergi
Merci @Bergi, je n'ai même pas réalisé que c'était un anti-modèle jusqu'à ce que vous en parliez!
Jay
c'est une bonne solution de contournement pour atténuer l'erreur liée à la promesse. J'utilisais ES5 et je ne voulais pas ajouter une autre bibliothèque pour travailler avec promesse.
nilakantha singh deo
8

Le nœud 7.4 prend désormais en charge les appels asynchrones / en attente avec l'indicateur d'harmonie.

Essaye ça:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

et exécutez le fichier avec:

node --harmony-async-await getExample.js

Aussi simple que cela puisse être!

Anthony
la source
8

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:

const firstPromise = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('first promise is completed');
            resolve({data: '123'});
        }, 2000);
    });
};

const secondPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('second promise is completed');
            resolve({newData: `${someStuff.data} some more data`});
        }, 2000);
    });
};

const thirdPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('third promise is completed');
            resolve({result: someStuff});
        }, 2000);
    });
};

firstPromise()
    .then(secondPromise)
    .then(thirdPromise)
    .then(data => {
        console.log(data);
    });
yzfdjzwl
la source
4
Cela ne répond pas vraiment à la question de savoir comment accéder aux résultats précédents de la chaîne.
Bergi
2
Chaque promesse peut avoir la valeur précédente, quelle est votre signification?
yzfdjzwl
1
Jetez un œil au code de la question. Le but n'est pas d'obtenir le résultat de la promesse invoquée .then, mais le résultat d'avant. Par exemple, thirdPromiseaccéder au résultat de firstPromise.
Bergi
6

Une autre réponse, en utilisant la babel-nodeversion <6

En utilisant async - await

npm install -g [email protected]

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Ensuite, courez babel-node example.jset le tour est joué!

Anthony
la source
1
Oui, juste après avoir posté le mien. Pourtant, je vais le laisser parce qu'il explique comment être réellement opérationnel avec ES7 au lieu de simplement dire qu'un jour ES7 sera disponible.
Anthony
1
Ah oui, je devrais mettre à jour ma réponse pour dire que les plugins "expérimentaux" pour ceux - ci sont déjà là.
Bergi
2

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.

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});
Anthony
la source
2
Notez que ce modèle est déjà détaillé dans la réponse d' état contextuel de Mutable (et aussi pourquoi il est moche - je ne suis pas non plus un grand fan)
Bergi
Dans votre cas, le motif semble cependant inutile. Vous n'avez pas besoin globalVardu tout, il suffit de faire User.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });?
Bergi
1
Je n'en ai pas besoin personnellement dans mon propre code, mais l'utilisateur peut avoir besoin d'exécuter plus d'async dans la deuxième fonction, puis d'interagir avec l'appel Promise d'origine. Mais comme mentionné, je vais utiliser des générateurs dans ce cas. :)
Anthony
2

Une autre réponse, en utilisant l'exécuteur séquentiel nsynjs :

function getExample(){

  var response1 = returnPromise1().data;

  // promise1 is resolved at this point, '.data' has the result from resolve(result)

  var response2 = returnPromise2().data;

  // promise2 is resolved at this point, '.data' has the result from resolve(result)

  console.log(response, response2);

}

nynjs.run(getExample,{},function(){
    console.log('all done');
})

Mise à jour: exemple de travail ajouté

function synchronousCode() {
     var urls=[
         "https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"
     ];
     for(var i=0; i<urls.length; i++) {
         var len=window.fetch(urls[i]).data.text().data.length;
         //             ^                   ^
         //             |                   +- 2-nd promise result
         //             |                      assigned to 'data'
         //             |
         //             +-- 1-st promise result assigned to 'data'
         //
         console.log('URL #'+i+' : '+urls[i]+", length: "+len);
     }
}

nsynjs.run(synchronousCode,{},function(){
    console.log('all done');
})
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

amaksr
la source
1

Lorsque vous utilisez bluebird, vous pouvez utiliser la .bindméthode pour partager des variables dans la chaîne de promesses:

somethingAsync().bind({})
.spread(function (aValue, bValue) {
    this.aValue = aValue;
    this.bValue = bValue;
    return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
    return this.aValue + this.bValue + cValue;
});

veuillez consulter ce lien pour plus d'informations:

http://bluebirdjs.com/docs/api/promise.bind.html

alphakevin
la source
Notez que ce modèle est déjà détaillé dans la réponse d' état contextuel
Mutable
1
function getExample() {
    var retA, retB;
    return promiseA(…).then(function(resultA) {
        retA = resultA;
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        //retA is value of promiseA
        return // How do I gain access to resultA here?
    });
}

moyen facile: D

Minh Giang
la source
Vous avez remarqué cette réponse ?
Bergi
1

Je pense que vous pouvez utiliser le hachage de RSVP.

Quelque chose comme ci-dessous:

    const mainPromise = () => {
        const promise1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('first promise is completed');
                resolve({data: '123'});
            }, 2000);
        });

        const promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('second promise is completed');
                resolve({data: '456'});
            }, 2000);
        });

        return new RSVP.hash({
              prom1: promise1,
              prom2: promise2
          });

    };


   mainPromise()
    .then(data => {
        console.log(data.prom1);
        console.log(data.prom2);
    });
Vishu
la source
Oui, c'est la même chose que la Promise.allsolution , uniquement avec un objet au lieu d'un tableau.
Bergi
0

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:

// Get info asynchronously from a server
function pGetServerInfo()
    {
    // then value: "server info"
    } // pGetServerInfo

// Write into a file asynchronously
function pWriteFile(path,string)
    {
    // no then value
    } // pWriteFile

// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
    {
    var scope={localInfo:localInfo}; // Create an explicit scope object
    var thenFunc=p2.bind(scope); // Create a temporary function with this scope
    return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
    } // pLogInfo

// Scope of this 'then' function is {localInfo:localInfo}
function p2(serverInfo)
    {
    // Do the final 'then' in the chain: Writes "local info, server info"
    return pWriteFile('log',this.localInfo+','+serverInfo);
    } // p2

Cette solution peut être invoquée comme suit:

pLogInfo("local info").then().catch(err);

(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.)

David Spector
la source
Cela semble être le même schéma que dans la réponse aux fermetures de nidification (et)
Bergi
Cela semble similaire. J'ai depuis appris que la nouvelle syntaxe Async / Await inclut la liaison automatique des arguments, donc tous les arguments sont disponibles pour toutes les fonctions asynchrones. J'abandonne les promesses.
David Spector
async/ awaitsignifie toujours utiliser des promesses. Ce que vous pourriez abandonner, ce sont les thenappels avec rappels.
Bergi
-1

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

/**
 * Promise like object that allows to resolve it promise from outside code. Example:
 *
```
class Api {
  fooReady = new Deferred<Data>()
  private knower() {
    inOtherMoment(data=>{
      this.fooReady.resolve(data)
    })
  }
}
```
 */
var Deferred = /** @class */ (function () {
  function Deferred(callback) {
    var instance = this;
    this.resolve = null;
    this.reject = null;
    this.status = 'pending';
    this.promise = new Promise(function (resolve, reject) {
      instance.resolve = function () { this.status = 'resolved'; resolve.apply(this, arguments); };
      instance.reject = function () { this.status = 'rejected'; reject.apply(this, arguments); };
    });
    if (typeof callback === 'function') {
      callback.call(this, this.resolve, this.reject);
    }
  }
  Deferred.prototype.then = function (resolve) {
    return this.promise.then(resolve);
  };
  Deferred.prototype.catch = function (r) {
    return this.promise.catch(r);
  };
  return Deferred;
}());

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=

cancerbero
la source
On dirait que vous suggérez un état contextuel mutable ou une inspection synchrone ?
Bergi
@bergi La première fois que je dirige ces noms, en ajoutant à la liste merci, je connais ce genre de promesses conscientes du nom de Deferred - BTW, l'implémentation est juste une promesse avec une résolution enveloppée. J'ai souvent besoin de ce modèle dans les cas où la responsabilité de la création et de la résolution des promesses est indépendante, il n'est donc pas nécessaire de les relier uniquement pour résoudre une promesse. J'ai adapté mais pas pour votre exemple, et en utilisant une classe, mais peut-être équivalent.
cancerbero