Come posso accedere ai precedenti risultati di promise in una catena .then ()?

Ho ristrutturato il mio codice in base alle promesse e ho creato una meravigliosa catena di promise lunga e piatta , composta da più callback .then() . Alla fine voglio restituire un po ‘di valore composito e ho bisogno di accedere a più risultati intermedi di promise . Tuttavia, i valori di risoluzione dal centro della sequenza non sono compresi nell’ultimo callback, come posso accedervi?

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

Rompere le catene

Quando devi accedere ai valori intermedi della tua catena, devi dividere la catena in quei pezzi singoli di cui hai bisogno. Anziché associare una richiamata e in qualche modo provare a utilizzare il suo parametro più volte, albind più callback alla stessa promise, ovunque sia necessario il valore del risultato. Non dimenticare, una promise rappresenta solo (proxy) un valore futuro ! Accanto a derivare una promise dall’altra in una catena lineare, usa i promettenti combinatori che ti vengono dati dalla tua biblioteca per build il valore del risultato.

Ciò si tradurrà in un stream di controllo molto semplice, una chiara composizione delle funzionalità e quindi una facile modularizzazione.

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

Invece del parametro destructuring nel callback dopo Promise.all che è diventato disponibile solo con ES6, in ES5 la chiamata sarebbe stata sostituita da un metodo helper nifty fornito da molte librerie promettenti ( Q , Bluebird , when , …):. .spread(function(resultA, resultB) { …

Bluebird dispone anche di una funzione di join dedicata per sostituire quella combinazione Promise.all + spread con un costrutto più semplice (e più efficiente):

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

ECMAScript Harmony

Naturalmente, questo problema è stato riconosciuto anche dai designer di lingue. Hanno fatto un sacco di lavoro e la proposta di funzioni asincrone ce l’ha fatta finalmente

ECMAScript 8

Non è più necessaria una singola funzione di richiamo o di callback, poiché in una funzione asincrona (che restituisce una promise quando viene chiamata) è ansible semplicemente attendere che le promesse si risolvano direttamente. Dispone inoltre di strutture di controllo arbitrarie come condizioni, loop e clausole try-catch, ma per comodità non ne abbiamo bisogno qui:

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

ECMAScript 6

Mentre stavamo aspettando ES8, abbiamo già usato un tipo molto simile di syntax. ES6 è venuto con funzioni generatore , che permettono di spezzare l’esecuzione in pezzi a parole chiave di yield posizionate arbitrariamente. Queste sezioni possono essere eseguite una dopo l’altra, indipendentemente, anche in modo asincrono – ed è proprio quello che facciamo quando vogliamo aspettare una risoluzione di promise prima di eseguire il passaggio successivo.

Ci sono librerie dedicate (come co o task.js ), ma anche molte librerie di promesse hanno funzioni di aiuto ( Q , Bluebird , quando , …) che eseguono questa asimmetrica esecuzione passo passo quando si dà loro una funzione di generatore che produce promesse.

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

Questo ha funzionato in Node.js dalla versione 4.0, anche alcuni browser (o le loro edizioni di sviluppo) hanno supportato la syntax del generatore relativamente presto.

ECMAScript 5

Tuttavia, se vuoi / devi essere retrocompatibile, non puoi usare quelli senza transpiler. Sia le funzioni del generatore che le funzioni asincrone sono supportate dagli attuali strumenti, vedi ad esempio la documentazione di Babel su generatori e funzioni asincrone .

E poi, ci sono anche molti altri linguaggi compile-to-JS che sono dedicati ad agevolare la programmazione asincrona. Solitamente usano una syntax simile await , (ad esempio Iced CoffeeScript ), ma ce ne sono anche altri che presentano una notazione Haskell-like (ad esempio LatteJs , monadic , PureScript o LispyScript ).

Ispezione sincrona

Assegnazione di promesse per valori successivi alle variabili e ottenimento del loro valore tramite ispezione sincrona. L’esempio usa il metodo .value() di bluebird ma molte librerie forniscono un metodo simile.

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

Questo può essere usato per tutti i valori che vuoi:

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

Nidificazione (e) chiusure

L’uso di chiusure per mantenere l’ambito delle variabili (nel nostro caso, i parametri della funzione di callback di successo) è la soluzione JavaScript naturale. Con le promesse, possiamo arbitrariamente annidare e appiattire i callback .then() – sono semanticamente equivalenti, tranne che per lo scopo di quello interno.

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

Certo, questo sta costruendo una piramide a indentazione. Se la rientranza sta diventando troppo grande, puoi comunque applicare i vecchi strumenti per contrastare la piramide di doom : modularizza, usa funzioni con un nome extra e appiattisci la catena di promesse non appena non hai più bisogno di una variabile.
In teoria, puoi sempre evitare più di due livelli di nidificazione (rendendo esplicite tutte le chiusure), in pratica usa tante quante sono ragionevoli.

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

È inoltre ansible utilizzare le funzioni di supporto per questo tipo di applicazione parziale , come _.partial da Underscore / lodash o il metodo nativo .bind() , per ridurre ulteriormente il rientro:

 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 } 

Passaggio esplicito

Simile alla nidificazione dei callback, questa tecnica si basa sulle chiusure. Tuttavia, la catena rimane piatta – invece di passare solo il risultato più recente, qualche object di stato viene passato per ogni passo. Questi oggetti di stato accumulano i risultati delle azioni precedenti, tramandando tutti i valori che saranno necessari in seguito più il risultato dell’attività corrente.

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

Qui, quella piccola freccia b => [resultA, b] è la funzione che si chiude su resultA e passa una matrice di entrambi i risultati al passo successivo. Che utilizza la syntax di destrutturazione dei parametri per suddividerla nuovamente in singole variabili.

Prima che la destrutturazione diventasse disponibile con ES6, un .spread() metodo di supporto chiamato .spread() era fornito da molte librerie di promesse ( Q , Bluebird , quando , …). Prende una funzione con più parametri – uno per ogni elemento dell’array – da usare come .spread(function(resultA, resultB) { …

Naturalmente, la chiusura necessaria qui può essere ulteriormente semplificata da alcune funzioni di supporto, ad es

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

In alternativa, è ansible utilizzare Promise.all per produrre la promise per l’array:

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

E potresti non usare solo matrici, ma oggetti arbitrariamente complessi. Ad esempio, con _.extend o Object.assign in una diversa funzione helper:

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

Mentre questo modello garantisce una catena piatta e oggetti di stato espliciti possono migliorare la chiarezza, diventerà noioso per una lunga catena. Soprattutto quando hai bisogno dello stato solo sporadicamente, devi comunque passare attraverso ogni fase. Con questa interfaccia fissa, i singoli callback della catena sono piuttosto strettamente accoppiati e inflessibili da cambiare. Rende più difficile il factoring dei singoli passaggi, e le callback non possono essere fornite direttamente da altri moduli: devono sempre essere racchiuse in un codice boilerplate che si preoccupa dello stato. Le funzioni di aiuto astratto come sopra possono alleviare un po ‘il dolore, ma saranno sempre presenti.

Stato contestuale mutabile

La soluzione banale (ma inelegante e piuttosto errorprone) consiste nell’usare solo variabili di ambito superiore (a cui tutte le callback nella catena hanno accesso) e scrivere loro i valori dei risultati quando le ottieni:

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

Invece di molte variabili si potrebbe anche usare un object (inizialmente vuoto), sul quale i risultati sono archiviati come proprietà create dynamicmente.

Questa soluzione ha diversi inconvenienti:

  • Lo stato mutevole è brutto e le variabili globali sono malvagie .
  • Questo modello non funziona oltre i limiti delle funzioni, la modularizzazione delle funzioni è più difficile in quanto le loro dichiarazioni non devono lasciare l’ambito condiviso
  • L’ambito delle variabili non impedisce di accedervi prima che siano inizializzati. Ciò è particolarmente probabile per le costruzioni di promesse complesse (loop, ramificazioni, ecc.) In cui potrebbero verificarsi condizioni di gara. Passando esplicitamente allo stato, un disegno dichiarativo che promette di incoraggiare, costringe uno stile di codifica più pulito che può impedirlo.
  • Uno deve scegliere l’ambito per quelle variabili condivise correttamente. Deve essere locale alla funzione eseguita per prevenire le condizioni di gara tra più chiamate parallele, come nel caso in cui, ad esempio, lo stato fosse memorizzato su un’istanza.

La libreria Bluebird incoraggia l’uso di un object che viene passato, usando il loro metodo bind() per assegnare un object contesto ad una catena di promesse. Sarà accessibile da ogni funzione di callback tramite la parola chiave altrimenti inutilizzabile. Mentre le proprietà degli oggetti sono più inclini a errori di battitura non rilevati rispetto alle variabili, il modello è abbastanza intelligente:

 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 } 

Questo approccio può essere facilmente simulato in librerie di promesse che non supportano .bind (anche se in un modo un po ‘più prolisso e non possono essere usate in un’espressione):

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

Il nodo 7.4 ora supporta chiamate asincrone / attese con il flag harmony.

Prova questo:

 async function getExample(){ let response = await returnPromise(); let response2 = await returnPromise2(); console.log(response, response2) } getExample() 

ed esegui il file con:

node --harmony-async-await getExample.js

Semplice come può essere!

Un’altra risposta, usando la versione del babel-node <6

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

Quindi, esegui babel-node example.js e voilà!

Una rotazione meno dura su “Mutual contextual state”

L’utilizzo di un object con ambito locale per raccogliere i risultati intermedi in una catena di promesse è un approccio ragionevole alla domanda che hai posto. Considera il seguente frammento:

 function getExample(){ //locally scoped const results = {}; return promiseA(...).then(function(resultA){ results.a = resultA; return promiseB(...); }).then(function(resultB){ results.b = resultB; return promiseC(...); }).then(function(resultC){ //Resolve with composite of all promises return Promise.resolve(results.a + results.b + resultC); }).catch(function(error){ return Promise.reject(error); }); } 
  • Le variabili globali sono cattive, quindi questa soluzione utilizza una variabile con ambito locale che non provoca danni. È accessibile solo all’interno della funzione.
  • Lo stato mutevole è brutto, ma questo non cambia lo stato in modo brutto. Lo stato brutto mutabile si riferisce tradizionalmente alla modifica dello stato degli argomenti della funzione o delle variabili globali, ma questo approccio modifica semplicemente lo stato di una variabile con scope locale che esiste al solo scopo di aggregare i risultati della promise … una variabile che morirà di una morte semplice una volta che la promise si risolverà.
  • Alle promesse intermedie non è impedito di accedere allo stato dell’object dei risultati, ma questo non introduce uno scenario spaventoso in cui una delle promesse della catena diventerà canaglia e sabotare i risultati. La responsabilità di impostare i valori in ogni fase della promise è limitata a questa funzione e il risultato complessivo sarà corretto o errato … non sarà un errore che si ripeterà anni dopo nella produzione (a meno che non lo si intenda !)
  • Questo non introduce uno scenario di condizioni di competizione che deriverebbe dall’invocazione parallela perché viene creata una nuova istanza della variabile di risultati per ogni chiamata della funzione getExample.

Un’altra risposta, utilizzando sequential executor 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'); }) 

Aggiornamento: aggiunto esempio di lavoro

 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 
  

Non userò questo modello nel mio codice poiché non sono un grande fan dell’uso di variabili globali. Tuttavia, in un pizzico funzionerà.

L’utente è un modello Mongoose promisurato.

 var globalVar = ''; User.findAsync({}).then(function(users){ globalVar = users; }).then(function(){ console.log(globalVar); }); 

Quando si utilizza bluebird, è ansible utilizzare il metodo .bind per condividere le variabili nella catena di promesse:

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

si prega di controllare questo link per ulteriori informazioni:

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

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

modo semplice: D

In questi giorni, ho anche incontrato alcune domande come te. Alla fine, trovo una buona soluzione con la domanda, è semplice e buona da leggere. Spero che ciò possa aiutarti.

Secondo how-to-chain-javascript-promesse

ok, guardiamo il codice:

 const firstPromise = () => { const promise = new Promise((resolve, reject) => { setTimeout(() => { console.log('first promise is completed'); resolve({data: '123'}); }, 2000); }); }; const secondPromise = (someStuff) => { const promise = new Promise((resolve, reject) => { setTimeout(() => { console.log('second promise is completed'); resolve({newData: `${someStuff.data} some more data`}); }, 2000); }); }; const thirdPromise = (someStuff) => { const promise = new Promise((resolve, reject) => { setTimeout(() => { console.log('third promise is completed'); resolve({result: someStuff}); }, 2000); }); }; firstPromise() .then(seondPromise) .then(thirdPromise) .then(data => { console.log(data); }); 

Penso che tu possa usare l’hash di RSVP.

Qualcosa come di seguito:

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