web-dev-qa-db-fra.com

Comment restituer correctement plusieurs valeurs d'une promesse?

Je suis récemment tombé sur une certaine situation à quelques reprises, que je ne savais pas comment résoudre correctement. Supposons le code suivant:

somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( amazingData ) {
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
}

Il se peut maintenant que je souhaite avoir accès à amazingData in afterSomethingElse.

Une solution évidente serait de renvoyer un tableau ou un hachage de afterSomething, car, eh bien, vous ne pouvez renvoyer qu'une valeur d'une fonction. Mais je me demande s’il est possible d’autoriser afterSomethingElse à accepter 2 paramètres et à les invoquer de la même manière, car cela semble beaucoup plus facile à documenter et à comprendre.

Je m'interroge seulement sur cette possibilité car il existe Q.spread, qui fait quelque chose de similaire à ce que je veux.

55
Der Hochstapler

Vous ne pouvez pas résoudre une promesse avec plusieurs propriétés, tout comme vous ne pouvez pas renvoyer plusieurs valeurs à partir d'une fonction. Une promesse représente conceptuellement une valeur dans le temps. Ainsi, même si vous pouvez représenter des valeurs composites, vous ne pouvez pas placer plusieurs valeurs dans une promesse.

Une promesse se résout intrinsèquement avec une seule valeur - cela fait partie du fonctionnement de Q, comment fonctionne la promesse/A + spec et de la façon dont l'abstraction _.

Le plus proche que vous puissiez obtenir est d'utiliser Q.spread et renvoyer des tableaux ou d'utiliser la déstructuration ES6 si elle est prise en charge ou si vous souhaitez utiliser un outil de transpilation tel que BabelJS.

En ce qui concerne le passage du contexte dans une chaîne de promesses, veuillez vous référer à l'excellent canonique de Bergi à ce sujet }.

63
Benjamin Gruenbaum

vous ne pouvez transmettre qu'une seule valeur, mais il peut s'agir d'un tableau contenant plusieurs valeurs, par exemple:

function step1(){
  let server = "myserver.com";
  let data = "so much data, very impresive";
  return Promise.resolve([server, data]);
}

de l'autre côté, vous pouvez utiliser l'expression destructuring pour ES2015 pour obtenir les valeurs individuelles.

function step2([server, data]){
  console.log(server); // print "myserver.com"
  console.log(data);   // print "so much data, very impresive"
  return Promise.resolve("done");
}

appeler les deux promesses, en les chaînant:

step1()
.then(step2)
.then((msg)=>{
  console.log(msg); // print "done"
})
18
Alejandro Silva

Vous pouvez retourner un objet contenant les deux valeurs - il n'y a rien de mal à cela.

Une autre stratégie consiste à conserver la valeur, via des fermetures, au lieu de la transmettre:

somethingAsync().then(afterSomething);

function afterSomething(amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

Formulaire entièrement en ligne plutôt que partiellement (équivalent, peut-être plus cohérent):

somethingAsync().then(function (amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}
18
Kevin Reid

Deux choses que vous pouvez faire, renvoyer un objet

somethingAsync()
    .then( afterSomething )
    .then( afterSomethingElse );

function processAsync (amazingData) {
     //processSomething
     return {
         amazingData: amazingData, 
         processedData: processedData
     };
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}

function afterSomethingElse( dataObj ) {
    let amazingData = dataObj.amazingData,
        processedData = dataObj.proccessedData;
}

Utilisez la portée!

var amazingData;
somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( returnedAmazingData ) {
  amazingData = returnedAmazingData;
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
  //use amazingData here
}
5
jemiloii

Créez simplement un objet et extrayez des arguments à partir de cet objet.

let checkIfNumbersAddToTen = function (a, b) {
return new Promise(function (resolve, reject) {
 let c = parseInt(a)+parseInt(b);
 let promiseResolution = {
     c:c,
     d : c+c,
     x : 'RandomString'
 };
 if(c===10){
     resolve(promiseResolution);
 }else {
     reject('Not 10');
 }
});
};

Extrait les arguments de promiseResolution.

checkIfNumbersAddToTen(5,5).then(function (arguments) {
console.log('c:'+arguments.c);
console.log('d:'+arguments.d);
console.log('x:'+arguments.x);
},function (failure) {
console.log(failure);
});
2
user6507599

Voici comment je pense que vous devriez faire.

couper la chaîne

Étant donné que les deux fonctions utiliseront amazingData, il est logique de les utiliser dans une fonction dédiée . Je le fais habituellement à chaque fois que je veux réutiliser des données, elles sont donc toujours présentes sous la forme d'une fonction arg.

Comme votre exemple utilise du code, je suppose que tout est déclaré dans une fonction. Je l'appellerai toto () . Ensuite, nous aurons une autre fonction qui exécutera à la fois afterSomething () et afterSomethingElse ().

function toto() {
    return somethingAsync()
        .then( tata );
}

Vous remarquerez également que j'ai ajouté une déclaration return, car il s'agit généralement du chemin à suivre avec les promesses - vous retournez toujours une promesse afin que nous puissions continuer à nous enchaîner si nécessaire. Ici, quelquechoseAsync () produira amazingData et sera disponible partout dans la nouvelle fonction.

Maintenant, à quoi ressemblera cette nouvelle fonction dépend généralement de is processAsync () est-il également asynchrone?

processAsync pas asynchrone

Aucune raison de trop compliquer les choses si processAsync () n'est pas asynchrone. Un bon vieux code séquentiel suffirait.

function tata( amazingData ) {
    var processed = afterSomething( amazingData );
    return afterSomethingElse( amazingData, processed );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

Notez que peu importe si afterSomethingElse () fait quelque chose d'async ou pas. Si tel est le cas, une promesse sera retournée et la chaîne pourra continuer. Si ce n'est pas le cas, la valeur du résultat sera renvoyée. Mais comme la fonction est appelée depuis un then (), la valeur sera quand même encapsulée dans une promesse (au moins en Javascript brut).

processAsync asynchrone

Si processAsync () est asynchrone, le code sera légèrement différent. Ici, nous considérons que afterSomething () et afterSomethingElse () ne seront pas réutilisés ailleurs.

function tata( amazingData ) {
    return afterSomething()
        .then( afterSomethingElse );

    function afterSomething( /* no args */ ) {
        return processAsync( amazingData );
    }
    function afterSomethingElse( processedData ) {
        /* amazingData can be accessed here */
    }
}

Comme auparavant pour afterSomethingElse (). Cela peut être asynchrone ou non. Une promesse sera retournée ou une valeur intégrée à une promesse résolue.


Votre style de codage est assez proche de ce que je faisais, c’est pourquoi j’ai répondu même après 2 ans .. Je ne suis pas un grand fan des fonctions anonymes partout. J'ai du mal à lire. Même si c'est assez courant dans la communauté. C'est comme nous avons remplacé le callback-hell par un promise-purgatory.

J'aime aussi garder le nom des fonctions dans le short then. De toute façon, ils ne seront définis localement que… .. Et la plupart du temps, ils appelleront une autre fonction définie ailleurs, donc réutilisable, pour faire le travail… .. Je le fais même pour les fonctions ne comportant qu'un paramètre, alors besoin d’obtenir la fonction en entrée et en sortie lorsque j’ajoute/supprime un paramètre à la signature de la fonction.

Exemple de manger

Voici un exemple:

function goingThroughTheEatingProcess(plenty, of, args, to, match, real, life) {
    return iAmAsync()
        .then(chew)
        .then(swallow);

        function chew(result) {
            return carefullyChewThis(plenty, of, args, "water", "piece of tooth", result);
        }

        function swallow(wine) {
            return nowIsTimeToSwallow(match, real, life, wine);
        }
}

function iAmAsync() {
    return Promise.resolve("mooooore");
}

function carefullyChewThis(plenty, of, args, and, some, more) {
    return true;
}

function nowIsTimeToSwallow(match, real, life, bobool) {
}

Ne vous concentrez pas trop sur le Promise.resolve (). C’est juste un moyen rapide de créer une promesse résolue… .. Ce que j’essaie d’obtenir par là est d’avoir tout le code que j’exécute dans un seul emplacement, juste sous le thens les autres fonctions avec un nom plus descriptif sont réutilisables.

L’inconvénient de cette technique est qu’elle définit beaucoup de fonctions . Mais c’est une douleur nécessaire pour éviter d’avoir des fonctions anonymes un peu partout . Et quel est le risque quand même: a débordement de pile? (blague!)


L'utilisation de tableaux ou d'objets tels que définis dans d'autres réponses fonctionnerait également. Celui-ci est en quelque sorte la réponse proposée par Kevin Reid .

Vous pouvez également utiliser bind () ou Promise.all () . Notez qu'ils vous demanderont toujours de scinder votre code.

en utilisant bind

Si vous voulez garder vos fonctions réutilisables mais que vous n'avez pas vraiment besoin de garder ce qui est à l'intérieur du alors très court, vous pouvez utiliser bind ().

function tata( amazingData ) {
    return afterSomething( amazingData )
        .then( afterSomethingElse.bind(null, amazingData) );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

Pour rester simple, bind () ajoutera la liste des arguments (sauf le premier) à la fonction lorsqu’elle sera appelée.

en utilisant Promise.all

Dans votre message, vous avez mentionné l'utilisation de spread (). Je n'ai jamais utilisé le framework que vous utilisez, mais voici comment vous devriez pouvoir l'utiliser.

Certains croient que Promise.all () est la solution à tous les problèmes, elle mérite donc d'être mentionnée, je suppose.

function tata( amazingData ) {
    return Promise.all( [ amazingData, afterSomething( amazingData ) ] )
        .then( afterSomethingElse );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( args ) {
    var amazingData = args[0];
    var processedData = args[1];
}

Vous pouvez transmettre des données à Promise.all () - notez la présence du tableau - tant que les promesses sont faites, mais assurez-vous qu'aucune des promesses n'échoue, sinon le traitement cessera.

Et au lieu de définir de nouvelles variables à partir de l'argument args, vous devriez pouvoir utiliser spread () au lieu de then () pour toute sorte de travail fantastique.

1
gabriel

Tout ce que vous reviendrez d'une promesse sera intégré à une promesse d'être non emballé à la prochaine étape .then().

Cela devient intéressant lorsque vous devez retourner une ou plusieurs promesses avec une ou plusieurs valeurs synchrones telles que;

Promise.resolve([Promise.resolve(1), Promise.resolve(2), 3, 4])
       .then(([p1,p2,n1,n2]) => /* p1 and p2 are still promises */);

Dans ces cas, il serait essentiel d’utiliser Promise.all() pour obtenir les promesses p1 et p2 non emballées à la prochaine étape .then(), telles que

Promise.resolve(Promise.all([Promise.resolve(1), Promise.resolve(2), 3, 4]))
       .then(([p1,p2,n1,n2]) => /* p1 is 1, p2 is 2, n1 is 3 and n2 is 4 */);
0
Redu

Vous pouvez vérifier Observable représenté par Rxjs , vous permet de renvoyer plusieurs valeurs.

0
codelovesme