Come restituire correttamente più valori da una promessa?


87

Recentemente mi sono imbattuto in una certa situazione un paio di volte, che non sapevo come risolvere correttamente. Assumi il codice seguente:

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

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

Ora una situazione in cui potrebbe sorgere vorrei avere accesso amazingDataa afterSomethingElse.

Una soluzione ovvia sarebbe restituire un array o un hash da afterSomething, perché, beh, puoi restituire solo un valore da una funzione. Ma mi chiedo se c'è un modo per afterSomethingElseaccettare 2 parametri e invocarlo allo stesso modo, poiché sembra molto più facile da documentare e capire.

Mi chiedo solo questa possibilità poiché c'è Q.spread, che fa qualcosa di simile a quello che voglio.




La destrutturazione dell'assegnazione in ES6 sarebbe di aiuto. controlla qui
Ravi Teja

Risposte:


88

Non puoi risolvere una promessa con più proprietà proprio come non puoi restituire più valori da una funzione . Una promessa rappresenta concettualmente un valore nel tempo, quindi mentre puoi rappresentare valori compositi non puoi inserire più valori in una promessa.

Una promessa si risolve intrinsecamente con un singolo valore: questo fa parte di come funziona Q, come funziona la specifica Promises / A + e come funziona l'astrazione .

Il più vicino che puoi ottenere è usare Q.spreade restituire array o utilizzare la destrutturazione ES6 se è supportata o se sei disposto a utilizzare uno strumento di transpilation come BabelJS.

Per quanto riguarda la trasmissione del contesto in una catena di promesse, fare riferimento all'eccellente canonico di Bergi su questo .


16
Cosa c'è di sbagliato nel risolvere con un oggetto che ha più proprietà? Sembra un modo semplice per ottenere più valori da una risoluzione.
jfriend00

6
Va benissimo farlo
Benjamin Gruenbaum

È inoltre possibile estendere la promessa di avere .spread()come Bluebird mostrando in questa risposta correlato: stackoverflow.com/a/22776850/1624862
Kevin Ghadyani

Il comportamento di Promise.all () sembra contraddirlo. Promise.all([a, b, c]).then(function(x, y, z) {...})funziona correttamente in tutti i moderni motori Javascript con x, yez che valutano i valori risolti di a, be c. Quindi è più accurato dire che la lingua non ti consente di farlo facilmente (o in modo corretto) dal codice utente (perché puoi restituire una promessa direttamente da una clausola then, puoi racchiudere i tuoi valori in promesse e poi racchiudere quelli con Promise .all () per ottenere il comportamento desiderato, anche se in modo contorto).
Austin Hemmelgarn

3
@AustinHemmelgarn Questo è solo falso, Promise.allsoddisfa con un array . In Promise.all([a,b]).then((a, b) => bè undefined. Ecco perché devi fare .then(([a, b]) =>che è un incarico destrutturante
Benjamin Gruenbaum

39

puoi passare solo un valore, ma può essere un array con valori multipli all'interno, ad esempio:

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

dall'altro lato, è possibile utilizzare l' espressione destrutturante per ES2015 per ottenere i singoli valori.

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

chiamare entrambe le promesse, concatenandole:

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

5
Si chiama destrutturazione , non "decostruttore", e non è un operatore: - /
Bergi

3
A proposito, puoi usare la destrutturazione proprio nei parametri: function step2([server, data]) { …- in questo modo eviti anche di assegnare a globali implicite. E dovresti davvero usare returno Promise.resolve, non il new Promisecostruttore nei tuoi esempi.
Bergi

grazie @Bergi per le raccomandazioni!
Alejandro Silva

20

Puoi restituire un oggetto contenente entrambi i valori - non c'è niente di sbagliato in questo.

Un'altra strategia è mantenere il valore, tramite chiusure, invece di passarlo attraverso:

somethingAsync().then(afterSomething);

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

Forma completamente piuttosto che parzialmente inline (equivalente, probabilmente più coerente):

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

3
Va bene restituirne uno thendentro l'altro then? Non è un anti-pattern ?
robe007

come ha detto @ robe007, non sarebbe simile a "callback hell"? qui il tuo annidamento quindi si blocca invece di funzioni di callback, questo vanificherebbe lo scopo stesso di avere promesse
Dheeraj

5

Due cose che puoi fare, restituire un oggetto

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

Usa l'ambito!

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

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

3

Ecco come penso che dovresti fare.

spezzare la catena

Poiché entrambe le funzioni useranno amazingData , ha senso averle in una funzione dedicata. Di solito lo faccio ogni volta che voglio riutilizzare alcuni dati, quindi è sempre presente come funzione arg.

Poiché il tuo esempio esegue del codice, suppongo che sia tutto dichiarato all'interno di una funzione. Lo chiamerò toto () . Quindi avremo un'altra funzione che verrà eseguita sia afterSomething () che afterSomethingElse () .

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

Noterai anche che ho aggiunto una dichiarazione di ritorno poiché di solito è la strada da percorrere con Promises: restituisci sempre una promessa in modo che possiamo continuare a concatenare se necessario. Qui, somethingAsync () produrrà amazingData e sarà disponibile ovunque all'interno della nuova funzione.

Ora, ciò che questa nuova funzione apparirà in genere dipende da processAsync () anche asincrono ?

processAsync non asincrono

Nessun motivo per complicare eccessivamente le cose se processAsync () non è asincrono. Qualche vecchio buon codice sequenziale ce l'avrebbe fatta.

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

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

Nota che non importa se afterSomethingElse () sta facendo qualcosa di asincrono o meno. In caso affermativo, verrà restituita una promessa e la catena potrà continuare. In caso contrario, verrà restituito il valore del risultato. Ma poiché la funzione viene chiamata da then () , il valore verrà comunque racchiuso in una promessa (almeno in Javascript grezzo).

processAsync asynchronous

Se processAsync () è asincrono, il codice avrà un aspetto leggermente diverso. Qui consideriamo afterSomething () e afterSomethingElse () non verranno riutilizzati da nessun'altra parte.

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

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

Come prima per afterSomethingElse () . Può essere asincrono o no. Verrà restituita una promessa o un valore racchiuso in una promessa risolta.


Il tuo stile di codifica è abbastanza vicino a quello che faccio io, ecco perché ho risposto anche dopo 2 anni. Non sono un grande fan delle funzioni anonime ovunque. Trovo difficile leggere. Anche se è abbastanza comune nella comunità. È come abbiamo sostituito l' inferno di richiamo con un purgatorio di promesse .

Mi piace anche mantenere il nome delle funzioni nell'allora breve. Verranno comunque definiti solo localmente. E la maggior parte delle volte chiameranno un'altra funzione definita altrove - così riutilizzabile - per fare il lavoro. Lo faccio anche per le funzioni con un solo parametro, quindi non è necessario inserire e rimuovere la funzione quando aggiungo / rimuovo un parametro alla firma della funzione.

Esempio di cibo

Ecco un esempio:

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

Non concentrarti troppo su Promise.resolve () . È solo un modo rapido per creare una promessa risolta. Quello che cerco di ottenere con questo è avere tutto il codice che sto eseguendo in un'unica posizione, appena sotto i thens . Tutte le altre funzioni con un nome più descrittivo sono riutilizzabili.

Lo svantaggio di questa tecnica è che definisce molte funzioni. Ma è un dolore necessario, temo, per evitare di avere funzioni anonime ovunque. E comunque qual è il rischio: uno stack overflow? (scherzo!)


Anche l'uso di array o oggetti come definiti in altre risposte funzionerebbe. Questa in un certo senso è la risposta proposta da Kevin Reid .

Puoi anche usare bind () o Promise.all () . Nota che ti chiederanno comunque di dividere il tuo codice.

utilizzando bind

Se vuoi mantenere le tue funzioni riutilizzabili ma non hai davvero bisogno di mantenere ciò che è all'interno di allora molto breve, puoi usare bind () .

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

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

Per mantenerlo semplice, bind () anteporrà l'elenco di argomenti (eccetto il primo) alla funzione quando viene chiamata.

utilizzando Promise.all

Nel tuo post hai menzionato l'uso di spread () . Non ho mai usato il framework che stai usando, ma ecco come dovresti essere in grado di usarlo.

Alcuni credono che Promise.all () sia la soluzione a tutti i problemi, quindi credo che meriti di essere menzionato.

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

Puoi passare i dati a Promise.all () - nota la presenza dell'array - purché promesse, ma assicurati che nessuna delle promesse fallisca altrimenti si interromperà l'elaborazione.

E invece di definire nuove variabili dall'argomento args , dovresti essere in grado di usare spread () invece di then () per ogni tipo di lavoro fantastico.


3

Crea semplicemente un oggetto ed estrai argomenti da quell'oggetto.

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

Estrai argomenti da 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

Qualunque cosa tu ritorni da una promessa sarà racchiusa in una promessa da scartare alla prossima .then() fase .

Diventa interessante quando è necessario restituire una o più promesse insieme a uno o più valori sincroni come;

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

In questi casi sarebbe essenziale utilizzare Promise.all()per ottenere p1e p2promesse scartate nella .then()fase successiva come

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

1

Puoi controllare Observable rappresentato da Rxjs , ti consente di restituire più di un valore.


0

Restituisci semplicemente una tupla:

async add(dto: TDto): Promise<TDto> {
console.log(`${this.storeName}.add(${dto})`);
return firebase.firestore().collection(this.dtoName)
  .withConverter<TDto>(this.converter)
  .add(dto)
  .then(d => [d.update(this.id, d.id), d.id] as [any, string])
  .then(x => this.get(x[1]));

}

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.