Come posso accedere ai risultati delle promesse precedenti in una catena .then ()?


650

Ho ristrutturato il mio codice in base alle promesse e creato una meravigliosa catena di promesse lunga e piatta , composta da più .then()callback. Alla fine, voglio restituire un valore composito e devo accedere a più risultati intermedi promessi . Tuttavia, i valori di risoluzione dal centro della sequenza non rientrano nell'ambito dell'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?
    });
}

2
Questa domanda è davvero interessante e anche se è taggata javascript, è rilevante in un'altra lingua. Uso solo la risposta "rompi la catena" in java e jdeferred
gontard

Risposte:


377

Rompere le catene

Quando è necessario accedere ai valori intermedi nella catena, è necessario dividere la catena in quei singoli pezzi necessari. Invece di associare un callback e in qualche modo provare a utilizzare il suo parametro più volte, allegare più callback alla stessa promessa, ovunque sia necessario il valore del risultato. Non dimenticare, una promessa rappresenta solo (proxy) un valore futuro ! Accanto a derivare una promessa dall'altra in una catena lineare, usa i combinatori di promesse che ti sono stati dati dalla tua biblioteca per costruire il valore del risultato.

Ciò si tradurrà in un flusso 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 destrutturazione nella richiamata dopo Promise.allche è diventato disponibile solo con ES6, nel ES5 la thenchiamata sarebbe stato sostituito da un metodo di supporto ingegnoso che è stato fornito da molte biblioteche promessa ( Q , Bluebird , quando , ...): .spread(function(resultA, resultB) { ….

Bluebird offre anche una joinfunzione dedicata per sostituire quella Promise.all+ spreadcombinazione con un costrutto più semplice (e più efficiente):


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

1
Le funzioni all'interno dell'array sono eseguite in ordine?
spaventoso

6
@scaryguy: non ci sono funzioni nell'array, queste sono promesse. promiseAe promiseBsono le funzioni (che promettono di ritornare) qui.
Bergi,

2
@Roland Non ho mai detto che fosse :-) Questa risposta è stata scritta nell'era ES5 in cui nessuna promessa era nello standard ed è spreadstata super utile in questo schema. Per soluzioni più moderne vedi la risposta accettata. Tuttavia, ho già aggiornato la risposta esplicita passthrough e non c'è davvero alcun buon motivo per non aggiornare anche questa.
Bergi,

1
@reify No, non dovresti farlo , causerebbe problemi con i rifiuti.
Bergi,

1
Non capisco questo esempio. Se esiste una catena di istruzioni "then" che richiedono che i valori vengano propagati in tutta la catena, non vedo come questo risolva il problema. Una promessa che richiede un valore precedente NON PU be essere attivata (creata) fino a quando quel valore non è presente. Inoltre, Promise.all () aspetta semplicemente che tutte le promesse nella sua lista finiscano: non impone un ordine. Quindi ho bisogno di ogni funzione "successiva" per avere accesso a tutti i valori precedenti e non vedo come il tuo esempio lo faccia. Dovresti guidarci attraverso il tuo esempio, perché non ci credo o non lo capisco.
David Spector,

238

ECMAScript Harmony

Naturalmente, questo problema è stato riconosciuto anche dai progettisti del linguaggio. Hanno fatto molto lavoro e la proposta relativa alle funzioni asincrone è finalmente riuscita

ECMAScript 8

Non hai più bisogno di una singola thenchiamata o di una funzione di callback, poiché in una funzione asincrona (che restituisce una promessa quando viene chiamata) puoi semplicemente aspettare che le promesse si risolvano direttamente. Presenta anche 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 aspettavamo ES8, abbiamo già usato un tipo di sintassi molto simile. ES6 è venuto con le funzioni del generatore , che permettono di spezzare l'esecuzione a pezzi in yieldparole chiave posizionate arbitrariamente . Queste sezioni possono essere eseguite una dopo l'altra, in modo indipendente, anche in modo asincrono, ed è proprio quello che facciamo quando vogliamo attendere una risoluzione promettente prima di eseguire il passaggio successivo.

Ci sono librerie dedicate (come co o task.js ), ma anche molte librerie promesse hanno funzioni di supporto ( Q , Bluebird , quando , ...) che eseguono questa asincrona esecuzione passo-passo per te quando dai loro una funzione di generatore che fa 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 sintassi del generatore relativamente presto.

ECMAScript 5

Tuttavia, se si desidera / è necessario essere retrocompatibili, non è possibile utilizzarli senza un transpiler. Entrambe le funzioni del generatore e le funzioni asincrone sono supportate dagli strumenti attuali, vedere ad esempio la documentazione di Babel sui generatori e le funzioni asincrone .

E poi, ci sono anche molti altri linguaggi di compilazione in JS dedicati a facilitare la programmazione asincrona. Farebbero generalmente una sintassi simile a await, (es ghiacciato CoffeeScript ), ma vi sono anche altri che dispongono di un Haskell-like do-notation (es LatteJs , monadico , PureScript o LispyScript ).


@Bergi devi attendere l'esame della funzione asincrona getExample () dal codice esterno?
arisalexis,

@arisalexis: Sì, getExampleè ancora una funzione che restituisce una promessa, che funziona esattamente come le funzioni nelle altre risposte, ma con una sintassi migliore. È possibile effettuare awaituna chiamata in un'altra asyncfunzione o è possibile concatenare .then()il risultato.
Bergi,

1
Sono curioso, perché hai risposto alla tua domanda subito dopo averlo fatto? C'è qualche buona discussione qui, ma sono curioso. Forse hai trovato le tue risposte da solo dopo aver chiesto?
granmoe

@granmoe: ho pubblicato l'intera discussione apposta come obiettivo canonico duplicato
Bergi,

Esiste un modo (non troppo laborioso) per evitare di usare Promise.coroutine (cioè non usare Bluebird o un'altra libreria, ma solo JS semplice) nell'esempio di ECMAScript 6 con la funzione generatore? Avevo in mente qualcosa del genere steps.next().value.then(steps.next)...ma non funzionava.
uno studente non ha nome il

102

Ispezione sincrona

Assegnare valori di promesse-per-dopo-necessarie alle variabili e quindi ottenere il loro valore tramite ispezione sincrona. L'esempio usa il .value()metodo 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 desideri:

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

6
Questa è la mia risposta preferita: leggibile, estensibile e minima dipendenza dalle funzioni di libreria o lingua
Jason

13
@Jason: Uh, " minima dipendenza dalle funzionalità della libreria "? L'ispezione sincrona è una funzione di libreria e non abbastanza standard da avviare.
Bergi,

2
Penso che intendesse le caratteristiche specifiche della biblioteca
deathgaze,

54

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 nidificare e appiattire arbitrariamente i .then()callback: sono semanticamente equivalenti, ad eccezione dell'ambito 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 di rientro. Se il rientro sta diventando troppo grande, puoi ancora applicare i vecchi strumenti per contrastare la piramide del destino : modularizza, usa funzioni extra nominate e appiattisci la catena della promessa non appena non hai più bisogno di una variabile.
In teoria, puoi sempre evitare più di due livelli di annidamento (rendendo esplicite tutte le chiusure), in pratica utilizzane 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 possibile utilizzare le funzioni di supporto per questo tipo di applicazione parziale , come _.partialda Underscore / lodash o dal 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
}

5
Lo stesso suggerimento viene fornito come soluzione all'errore avanzato n. 4 nell'articolo di Nolan Lawson sulle promesse pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html . È una buona lettura
Robert

2
Questa è esattamente la bindfunzione in Monadi. Haskell fornisce zucchero sintattico (notazione) per farlo sembrare sintassi asincrona / attendi.
Zeronone,

50

Pass-through esplicito

Simile alla nidificazione dei callback, questa tecnica si basa su chiusure. Tuttavia, la catena rimane piatta - invece di passare solo il risultato più recente, viene passato un oggetto stato per ogni passaggio. Questi oggetti stato accumulano i risultati delle azioni precedenti, tramandando di nuovo 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 resultAe passa una matrice di entrambi i risultati al passaggio successivo. Che utilizza la sintassi destrutturante dei parametri per suddividerla nuovamente in singole variabili.

Prima che la destrutturazione diventasse disponibile con ES6, un ingegnoso metodo di supporto chiamato .spread()era fornito da molte librerie promesse ( Q , Bluebird , quando , ...). Ci vuole 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, è possibile utilizzare Promise.allper produrre la promessa 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 solo usare array, ma oggetti arbitrariamente complessi. Ad esempio, con _.extendo Object.assignin un'altra funzione di supporto:

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 passarlo attraverso ogni passo. Con questa interfaccia fissa, i singoli callback nella catena sono piuttosto strettamente accoppiati e non flessibili da modificare. Rende più difficile il factoring di singoli passaggi e i callback non possono essere forniti direttamente da altri moduli: devono sempre essere racchiusi nel codice del boilerplate che si preoccupa dello stato. Funzioni di supporto astratte come quelle sopra possono alleviare un po 'il dolore, ma sarà sempre presente.


In primo luogo, non credo che la sintassi che omette il Promise.alldovrebbe essere incoraggiata (non funzionerà in ES6 quando la destrutturazione lo sostituirà e il passaggio .spreada a thendà spesso risultati inaspettati alla gente. A partire da un aumento - Non sono sicuro del motivo per cui hai bisogno usare augment - aggiungere cose al prototipo della promessa non è un modo accettabile per estendere le promesse ES6 che dovrebbero essere estese con la sottoclasse (attualmente non supportata)
Benjamin Gruenbaum

@BenjaminGruenbaum: cosa intendi per " omissione della sintassiPromise.all "? Nessuno dei metodi in questa risposta si romperà con ES6. Anche il passaggio da spreada a destrutturazione thennon dovrebbe avere problemi. Re .prototype.augment: sapevo che qualcuno se ne sarebbe accorto, mi è solo piaciuto esplorare le possibilità - modificarlo.
Bergi,

Con la sintassi dell'array intendo return [x,y]; }).spread(...invece return Promise.all([x, y]); }).spread(...che non cambierebbe quando si scambiava la diffusione con lo zucchero destrutturante es6 e non sarebbe nemmeno un caso strano in cui le promesse trattano le matrici di ritorno in modo diverso da tutto il resto.
Benjamin Gruenbaum,

3
Questa è probabilmente la risposta migliore. Le promesse sono "Programmazione reattiva funzionale", e questa è spesso la soluzione utilizzata. Ad esempio, BaconJs ha #combineTemplate che ti consente di combinare i risultati in un oggetto che viene passato lungo la catena
U Avalos,

1
@CapiEtheriel La risposta è stata scritta quando ES6 non era così diffuso come lo è oggi. Sì, forse è tempo di scambiare gli esempi
Bergi,

35

Stato contestuale mutevole

La soluzione banale (ma inelegante e piuttosto soggetta a errori) è quella di utilizzare solo variabili di ambito superiore (a cui hanno accesso tutti i callback nella catena) e scrivere loro i valori dei risultati quando li 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 oggetto (inizialmente vuoto), sul quale i risultati sono memorizzati come proprietà create dinamicamente.

Questa soluzione presenta diversi inconvenienti:

  • Lo stato mutevole è brutto e le variabili globali sono cattive .
  • 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 vengano inizializzate. Ciò è particolarmente probabile per le complesse costruzioni di promesse (anelli, ramificazioni, eccezioni) in cui potrebbero verificarsi condizioni di gara. Il passaggio esplicito dello stato, un disegno dichiarativo che promette incoraggiamento, impone uno stile di codifica più pulito che può impedirlo.
  • Bisogna scegliere correttamente l'ambito per quelle variabili condivise. Deve essere locale alla funzione eseguita per prevenire le condizioni di competizione tra più invocazioni parallele, come nel caso in cui, ad esempio, lo stato fosse archiviato in un'istanza.

La libreria Bluebird incoraggia l'uso di un oggetto che viene passato, usando il loro bind()metodo per assegnare un oggetto di contesto a una catena di promesse. Sarà accessibile da ciascuna funzione di callback tramite la thisparola chiave altrimenti inutilizzabile . Mentre le proprietà degli oggetti sono più inclini ai refusi 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 promesse che non supportano .bind (anche se in modo un po 'più dettagliato e non possono essere utilizzate 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));
}

.bind()non è necessario per prevenire la perdita di memoria
Esailija,

@Esailija: Ma la promessa restituita non contiene altrimenti un riferimento all'oggetto contesto? OK, ovviamente la garbage collection lo gestirà in seguito; non è una "perdita" a meno che la promessa non venga mai eliminata.
Bergi,

Sì, ma le promesse fanno anche riferimento ai loro valori di adempimento e ragioni di errore ... ma nulla contiene riferimenti alla promessa quindi non importa
Esailija

4
Per favore, dividi questa risposta in due mentre ho quasi votato il preambolo! Penso che "la soluzione banale (ma inelegante e piuttosto soggetta a errori)" sia la soluzione più pulita e semplice, poiché non si basa più su chiusure e stato mutevole della tua risposta autorizzata accettata, ma è più semplice. Le chiusure non sono né globali né malvagie. Gli argomenti addotti contro questo approccio non hanno senso per me data la premessa. Quali problemi di modularizzazione si può dare una "meravigliosa catena di promesse long flat"?
fiocco

2
Come ho detto sopra, le promesse sono "Programmazione reattiva funzionale". Questo è un anti-pattern in FRP
U Avalos,

15

Un giro meno duro su "Stato contestuale mutevole"

L'uso di un oggetto 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(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);
    });
}
  • Le variabili globali sono dannose, quindi questa soluzione utilizza una variabile con ambito locale che non causa alcun danno. È accessibile solo all'interno della funzione.
  • Lo stato mutevole è brutto, ma questo non muta lo stato in modo brutto. Il brutto stato mutabile si riferisce tradizionalmente alla modifica dello stato degli argomenti delle funzioni o delle variabili globali, ma questo approccio modifica semplicemente lo stato di una variabile con ambito locale che esiste al solo scopo di aggregare i risultati della promessa ... una variabile che morirà per una semplice morte una volta risolta la promessa.
  • Alle promesse intermedie non viene impedito di accedere allo stato dell'oggetto risultati, ma ciò non introduce alcuni scenari spaventosi in cui una delle promesse nella catena diventerà canaglia e saboterà i risultati. La responsabilità di impostare i valori in ogni fase della promessa è limitata a questa funzione e il risultato complessivo sarà o corretto o errato ... non sarà un bug che sorgerà anni dopo nella produzione (a meno che tu non lo intenda !)
  • Ciò non introduce uno scenario delle condizioni di gara che deriverebbe dall'invocazione parallela poiché viene creata una nuova istanza della variabile dei risultati per ogni invocazione della funzione getExample.

1
Almeno evita l' Promiseantipasto del costruttore !
Bergi,

Grazie @Bergi, non mi ero nemmeno reso conto che era un anti-schema fino a quando non lo hai menzionato!
Jay,

questa è una buona soluzione per mitigare l'errore relativo alla promessa. Stavo usando ES5 e non volevo aggiungere un'altra libreria per lavorare con la promessa.
nilakantha singh deo,

8

Il nodo 7.4 ora supporta le chiamate asincrone / attendi con il flag di armonia.

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!


8

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 le promesse how-to-chain-javascript

ok, diamo un'occhiata al codice:

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

4
Questo in realtà non risponde alla domanda su come accedere ai risultati precedenti nella catena.
Bergi,

2
Ogni promessa può ottenere il valore precedente, qual è il tuo significato?
yzfdjzwl,

1
Dai un'occhiata al codice nella domanda. L'obiettivo non è quello di ottenere il risultato della promessa che .thenviene invocata, ma i risultati di prima. Ad esempio, l' thirdPromiseaccesso al risultato di firstPromise.
Bergi,

6

Un'altra risposta, utilizzando la babel-nodeversione <6

utilizzando async - await

npm install -g babel@5.6.14

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Quindi, corri babel-node example.jse voilà!


1
Sì, l'ho fatto subito dopo aver pubblicato il mio. Tuttavia, lo lascerò perché spiega come effettivamente iniziare a utilizzare ES7 invece di dire semplicemente che un giorno ES7 sarà disponibile.
Anthony,

1
Oh giusto, dovrei aggiornare la mia risposta per dire che i plugin "sperimentali" per questi sono già qui.
Bergi,

2

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

L'utente è un modello Mongoose promesso.

var globalVar = '';

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

2
Si noti che questo modello è già dettagliato nella risposta dello stato contestuale mutevole (e anche perché è brutto - non sono neanche un grande fan)
Bergi,

Nel tuo caso, il modello sembra essere inutile però. Non hai affatto bisogno globalVardi, basta User.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });?
Bergi,

1
Non ne ho bisogno personalmente nel mio codice, ma potrebbe essere necessario che l'utente esegua più asincrono nella seconda funzione e quindi interagisca con la chiamata Promise originale. Ma come detto, userò i generatori in questo caso. :)
Anthony,

2

Un'altra risposta, usando l'esecutore sequenziale 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 funzionante

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>


1

Quando si utilizza bluebird, è possibile utilizzare il .bindmetodo 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;
});

per favore controlla questo link per ulteriori informazioni:

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


Si noti che questo modello è già dettagliato nella risposta dello stato contestuale
mutevole

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

modo semplice: D


Hai notato questa risposta ?
Bergi

1

Penso che tu possa usare l'hash di RSVP.

Qualcosa come il seguente:

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

Sì, è la stessa della Promise.allsoluzione , solo con un oggetto anziché un array.
Bergi,

0

Soluzione:

È possibile inserire i valori intermedi nell'ambito in qualsiasi funzione successiva "then" esplicitamente, utilizzando "bind". È una buona soluzione che non richiede di cambiare il funzionamento di Promises e richiede solo una o due righe di codice per propagare i valori proprio come gli errori sono già propagati.

Ecco un esempio completo:

// 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

Questa soluzione può essere invocata come segue:

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

(Nota: è stata testata una versione più complessa e completa di questa soluzione, ma non questa versione di esempio, quindi potrebbe avere un bug.)


Questo sembra essere lo stesso modello della risposta di nidificazione (e) chiusure
Bergi

Sembra simile. Da allora ho appreso che la nuova sintassi Async / Await include l'associazione automatica degli argomenti, quindi tutti gli argomenti sono disponibili per tutte le funzioni asincrone. Sto abbandonando le promesse.
David Spector,

async/ awaitsignifica ancora usare le promesse. Ciò che potresti abbandonare sono le thenchiamate con richiamate.
Bergi,

-1

Ciò che apprendo sulle promesse è di usarlo solo come valori di ritorno , se possibile evitare di fare riferimento a tali valori . la sintassi async / waitit è particolarmente pratica per questo. Oggi tutti i browser e i nodi più recenti lo supportano: https://caniuse.com/#feat=async-functions , è un comportamento semplice e il codice è come leggere il codice sincrono, dimenticare i callback ...

Nei casi in cui ho bisogno di fare riferimento a una promessa è quando la creazione e la risoluzione avvengono in luoghi indipendenti / non correlati. Quindi invece un'associazione artificiale e probabilmente un ascoltatore di eventi solo per risolvere la promessa "distante", preferisco esporre la promessa come differita, che il codice seguente implementa in es5 valido

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

traspilato da un mio progetto dattiloscritto:

https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31

Per i casi più complessi uso spesso questi programmi di utilità di piccola promessa senza dipendenze testati e digitati. p-map è stata utile più volte. Penso che abbia coperto la maggior parte dei casi d'uso:

https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=


Sembra che tu stia suggerendo uno stato contestuale mutabile o un'ispezione sincrona ?
Bergi,

@bergi La prima volta che mi dirigo a quei nomi. Grazie all'elenco grazie. Conosco questo tipo di promesse auto-consapevoli con il nome di Deferred - BTW l'implementazione è solo una promessa con risolutezza. Ho spesso bisogno di questo modello nei casi in cui la responsabilità della creazione e della risoluzione delle promesse è indipendente, quindi non è necessario collegarle solo per risolvere una promessa. Mi sono adattato, ma non per il tuo esempio, e usando una classe, ma forse equivalente.
cancerbero,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.