javascript - les - settimeout




Comment accéder aux résultats des promesses précédentes dans une chaîne.then()? (12)

J'ai restructuré mon code en promises et construit une merveilleuse chaîne de promesses longue et plate , composée de multiples .then() . En fin de compte, je veux renvoyer une valeur composite et avoir 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 comprises dans le dernier rappel, comment puis-je 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?
    });
}

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 parties dont vous avez besoin. Au lieu d'attacher un rappel et d'essayer d'une manière ou d'une autre d'utiliser ses paramètres à plusieurs reprises, attachez plusieurs rappels à la même promesse, partout où vous avez besoin de la valeur du résultat. N'oubliez pas qu'une promesse représente (une procuration) une valeur future ! En plus de dériver une promesse de l’autre dans une chaîne linéaire, utilisez les combinateurs de promesse qui vous sont fournis par votre bibliothèque pour générer 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 aisée.

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.all qui n'est devenu disponible qu'avec ES6, dans ES5, l'appel then serait remplacé par une méthode d'assistance astucieuse fournie par de nombreuses bibliothèques de promesses ( Q , Bluebird , when ,…):. .spread(function(resultA, resultB) { … .

Bluebird propose également une fonction de join dédiée pour remplacer cette combinaison Promise.all + spread par une structure plus simple (et plus efficace):


return Promise.join(a, b, function(resultA, resultB) {  });

Etat contextuel modifiable

La solution triviale (mais peu élégante et plutôt sujette aux erreurs) consiste simplement à utiliser des variables de plus haute portée (auxquelles tous les callbacks ont accès) et à leur écrire des valeurs de résultat lorsque vous les recevez:

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, vous pouvez également utiliser un objet (initialement vide) sur lequel les résultats sont stockés sous forme de propriétés créées dynamiquement.

Cette solution présente plusieurs inconvénients:

  • L'état mutable est moche et les variables globales sont pervers .
  • 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, ramifications, excursions) où des conditions de concurrence pourraient se présenter. Passer explicitement à l’état, une conception déclarative encouragée par les promesses, impose un style de codage plus propre qui puisse l’empêcher.
  • Il faut choisir correctement la portée de ces variables partagées. Il doit être local à la fonction exécutée pour éviter les conditions de concurrence entre plusieurs invocations 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 qui est transmis, en utilisant leur méthode bind() pour affecter un objet de contexte à une chaîne de promesse. Il sera accessible à partir de chaque fonction de rappel via le mot clé inutilisable sinon. Bien que les propriétés des objets soient plus sujettes aux fautes de frappe non détectées qu'aux variables, le modèle est assez astucieux:

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 ne prenant pas en charge .bind (bien que d'une manière un peu plus détaillée et ne pouvant pas être utilisé 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));
}

Harmonie ECMAScript

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 a finalement été intégrée dans

ECMAScript 8

Vous n'avez plus besoin d'une seule fonction d'appel ou de rappel, car dans une fonction asynchrone (qui renvoie une promesse lors de l'appel), vous pouvez simplement attendre que les promesses soient résolues directement. Il comporte également des structures de contrôle arbitraires telles que des conditions, des boucles et des clauses try-catch-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

En attendant 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 fragmenter l'exécution en morceaux arbitrairement placés. Ces tranches peuvent être exécutées les unes après les autres, de manière indépendante, voire 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 (telles que co ou task.js ), mais de nombreuses bibliothèques de promesses ont des fonctions d'assistance ( Q , Bluebird , when ,…) qui effectuent cette exécution asynchrone, étape par étape, lorsque vous leur attribuez une fonction de générateur 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 dev) supportaient relativement tôt la syntaxe de générateur.

ECMAScript 5

Cependant, si vous voulez / avez besoin d'une compatibilité ascendante, vous ne pouvez pas utiliser ceux sans transpiler. L'outil actuel prend en charge les fonctions de generators et les fonctions asynchrones . Voir, par exemple, la documentation de Babel sur les generators et les fonctions asynchrones .

Et puis, de nombreux autres langages de compilation à JS sont dédiés à la simplification de la programmation asynchrone. Ils utilisent généralement une syntaxe similaire à await (par exemple, Iced CoffeeScript ), mais il en existe d'autres qui comportent une n- do -like de type Haskell (par exemple, PureScript , PureScript , PureScript ou LispyScript ).


Inspection synchrone

Affectation de valeurs promises aux variables nécessaires ultérieurement, puis obtention de leur valeur via l'inspection synchrone. L'exemple utilise la méthode .value() de .value() , mais de nombreuses bibliothèques fournissent 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();
    });
}

Ceci 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();
    });
}

Une version moins dure de "l'état contextuel modifiable"

Utiliser un objet de portée locale pour collecter les résultats intermédiaires dans une chaîne de promesses constitue une approche raisonnable de la question que vous avez posée. Considérez l'extrait 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, cette solution utilise donc une variable de portée locale qui ne cause aucun dommage. Il est uniquement accessible dans la fonction.
  • L'état mutable est moche, mais cela ne mute pas de manière moche. L'état déplorable moche se réfère traditionnellement à la modification de l'état des arguments 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 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 un scénario effrayant dans lequel l'une des promesses de la chaîne va devenir malveillante et saboter 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 soit correct, soit incorrect. Ce ne sera pas un bug qui apparaîtra des années plus tard dans la production (à moins que vous ne le souhaitiez. !)
  • Cela n'introduit pas de scénario de condition de concurrence qui résulterait d'un appel parallèle, car une nouvelle instance de la variable de résultats est créée pour chaque appel de la fonction getExample.

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>


Ce que j’apprends des promesses, c’est de ne les utiliser que comme valeurs de retour, évitez de les référencer si possible. La syntaxe async / wait est particulièrement pratique pour cela. Aujourd'hui, tous les navigateurs et nœuds actuels le prennent en charge: https://caniuse.com/#feat=async-functions , le comportement est simple et le code est comme si vous lisiez du code synchrone, oubliez les rappels ...

Dans les cas où j'ai besoin de 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 de cela une association artificielle et probablement un écouteur d’événement 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 en valide es5

 /** * 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é d'un projet dactylographié du mien:

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 utilitaires de petite promesse sans dépendances testées et dactylographié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=


Ces jours-ci, je dois aussi répondre à quelques questions comme vous. Enfin, je trouve une bonne solution avec la question, c'est simple et bon à lire. J'espère que ceci peut vous aider.

Selon how-to-chain-javascript-promises

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);
    });

Je pense que vous pouvez utiliser le hash de RSVP.

Quelque chose comme 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);
    });

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 lancez le fichier avec:

node --harmony-async-await getExample.js

Aussi simple que peut être!


Solution:

Vous pouvez mettre explicitement les valeurs intermédiaires dans la portée de toute fonction "then" ultérieure, en utilisant "bind". C'est une solution intéressante qui ne nécessite pas de modifier le fonctionnement de Promises et qui ne nécessite qu'une ligne ou deux 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 cette version, elle pourrait donc comporter un bogue.)


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





es6-promise