Como você retorna corretamente vários valores de uma promessa?

86

Recentemente, eu me deparei com uma determinada situação algumas vezes, que não sabia como resolver adequadamente. Suponha o seguinte código:

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

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

Agora a situação pode surgir onde eu gostaria de ter acesso a amazingDataem afterSomethingElse.

Uma solução óbvia seria retornar um array ou um hash de afterSomething, porque, bem, você só pode retornar um valor de uma função. Mas estou me perguntando se há uma maneira de afterSomethingElseaceitar 2 parâmetros e invocá-los da mesma forma, já que parece muito mais fácil de documentar e entender.

Só estou me perguntando sobre essa possibilidade, já que existe Q.spread, que faz algo semelhante ao que eu quero.

Der Hochstapler
fonte
A desestruturação da Atribuição no ES6 ajudaria. confira aqui
Ravi Teja

Respostas:

88

Você não pode resolver uma promessa com várias propriedades, assim como não pode retornar vários valores de uma função . Uma promessa representa conceitualmente um valor ao longo do tempo, portanto, embora você possa representar valores compostos, não pode colocar vários valores em uma promessa.

Uma promessa inerentemente resolve com um único valor - isso é parte de como Q funciona, como a especificação Promises / A + funciona e como a abstração funciona.

O mais próximo que você pode obter é usar Q.spreade retornar arrays ou usar a desestruturação do ES6 se houver suporte ou se você estiver disposto a usar uma ferramenta de transpilação como o BabelJS.

Quanto à passagem de contexto por uma cadeia de promessas, consulte o excelente cânone de Bergi sobre isso .

Benjamin Gruenbaum
fonte
16
O que há de errado em resolver com um objeto que possui várias propriedades? Parece uma maneira simples de obter vários valores de uma resolução.
jfriend00
6
É perfeitamente normal fazer isso
Benjamin Gruenbaum
Você também pode estender a promessa de ter .spread()como o Bluebird sendo mostrado nesta resposta relacionada: stackoverflow.com/a/22776850/1624862
Kevin Ghadyani
O comportamento de Promise.all () parece contradizer isso. Promise.all([a, b, c]).then(function(x, y, z) {...})funciona corretamente em todos os mecanismos Javascript modernos com x, y e z avaliando os valores resolvidos de a, b e c. Portanto, é mais preciso dizer que a linguagem não permite que você faça isso facilmente (ou sensatamente) a partir do código do usuário (porque você pode retornar uma Promise diretamente de uma cláusula then, você pode envolver seus valores em promessas e, em seguida, envolvê-los com Promise .all () para obter o comportamento desejado, embora de maneira complicada).
Austin Hemmelgarn
3
@AustinHemmelgarn Isso é simplesmente falso, Promise.allcumpre com uma série . Em Promise.all([a,b]).then((a, b) => bé undefined. É por isso que você precisa fazer, o .then(([a, b]) =>que é uma tarefa de desestruturação
Benjamin Gruenbaum
38

você só pode passar um valor, mas pode ser uma matriz com múltiplos valores, como exemplo:

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

do outro lado, você pode usar a desestruturação expressão para ES2015 para obter os valores individuais.

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

chamar ambos de promessa, encadeando-os:

step1()
.then(step2)
.then((msg)=>{
  console.log(msg); // print "done"
})
Alejandro Silva
fonte
5
É chamado de desestruturação , não "desconstrutor", e não é um operador: - /
Bergi
3
A propósito, você pode usar a desestruturação diretamente nos parâmetros: function step2([server, data]) { …- dessa forma, você também evita atribuir a globais implícitos. E você realmente deve usar returnor Promise.resolve, não o new Promiseconstrutor em seus exemplos.
Bergi,
Obrigado @Bergi pelas recomendações!
Alejandro Silva
19

Você pode retornar um objeto contendo ambos os valores - não há nada de errado com isso.

Outra estratégia é manter o valor, por meio de fechamentos, em vez de repassá-lo:

somethingAsync().then(afterSomething);

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

Forma totalmente em vez de parcialmente embutida (equivalente, provavelmente mais consistente):

somethingAsync().then(function (amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}
Kevin Reid
fonte
3
Posso devolver um thendentro do outro then? Não é um anti-padrão ?
robe007
como @ robe007 disse, isso não seria semelhante a 'callback hell'? aqui, seu aninhamento bloqueia em vez de funções de retorno de chamada, isso anularia o próprio propósito de ter promessas
Dheeraj
5

Duas coisas que você pode fazer, retornar um objeto

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

Use o escopo!

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

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

É assim que eu acho que você deveria estar.

dividindo a corrente

Como as duas funções usarão amazingData , faz sentido tê-las em uma função dedicada. Normalmente faço isso sempre que quero reutilizar alguns dados, por isso está sempre presente como uma função arg.

Como seu exemplo está executando algum código, suponho que tudo esteja declarado dentro de uma função. Vou chamá-lo de toto () . Então teremos outra função que irá rodar afterSomething () e afterSomethingElse () .

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

Você também notará que adicionei uma declaração de retorno , pois geralmente é a maneira de proceder com as Promessas - você sempre retorna uma promessa para que possamos continuar o encadeamento, se necessário. Aqui, somethingAsync () produzirá amazingData e estará disponível em qualquer lugar dentro da nova função.

Agora, como essa nova função normalmente depende se processAsync () também é assíncrona ?

processAsync não assíncrono

Não há razão para complicar as coisas se processAsync () não for assíncrono. Algum bom código sequencial antigo faria isso.

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

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

Observe que não importa se afterSomethingElse () está fazendo algo assíncrono ou não. Em caso afirmativo, uma promessa será devolvida e a cadeia pode continuar. Se não for, o valor do resultado será retornado. Mas como a função é chamada a partir de um then () , o valor será empacotado em uma promessa de qualquer maneira (pelo menos em Javascript bruto).

processAsync assíncrono

Se processAsync () for assíncrono, o código será um pouco diferente. Aqui, consideramos afterSomething () e afterSomethingElse () não serão reutilizados em nenhum outro lugar.

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

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

O mesmo que antes para afterSomethingElse () . Pode ser assíncrono ou não. Uma promessa será devolvida ou um valor embrulhado em uma promessa resolvida.


Seu estilo de codificação é bastante próximo ao que eu costumo fazer, por isso respondi mesmo depois de 2 anos. Não sou um grande fã de ter funções anônimas em todos os lugares. Acho difícil de ler. Mesmo que seja bastante comum na comunidade. É como substituímos o inferno de retorno por um purgatório de promessa .

Também gosto de manter o nome das funções no então curto. Eles só serão definidos localmente de qualquer maneira. E na maioria das vezes eles chamarão outra função definida em outro lugar - tão reutilizável - para fazer o trabalho. Eu até faço isso para funções com apenas 1 parâmetro, então não preciso colocar e tirar a função quando adiciono / removo um parâmetro à assinatura da função.

Comer exemplo

Aqui está um exemplo:

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

Não foque muito no Promise.resolve () . É apenas uma maneira rápida de criar uma promessa resolvida. O que tento conseguir com isso é ter todo o código que estou executando em um único local - logo abaixo do local . Todas as outras funções com um nome mais descritivo são reutilizáveis.

A desvantagem dessa técnica é que ela define muitas funções. Mas é uma dor necessária, receio, para não ter funções anônimas por todo lado. E qual é o risco de qualquer maneira: um estouro de pilha? (Piada!)


Usar matrizes ou objetos conforme definido em outras respostas também funcionaria. Essa de certa forma é a resposta proposta por Kevin Reid .

Você também pode usar bind () ou Promise.all () . Observe que eles ainda exigirão que você divida seu código.

usando ligação

Se você deseja manter suas funções reutilizáveis, mas não precisa realmente manter o que está dentro do então muito curto, você pode usar bind () .

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

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

Para mantê-lo simples, bind () adicionará a lista de args (exceto o primeiro) à função quando ela for chamada.

usando Promise.all

Em sua postagem, você mencionou o uso de spread () . Nunca usei a estrutura que você está usando, mas aqui está como você deve ser capaz de usá-la.

Alguns acreditam que Promise.all () é a solução para todos os problemas, então ele merece ser mencionado, eu acho.

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

Você pode passar dados para Promise.all () - observe a presença do array - contanto que as promessas, mas certifique-se de que nenhuma das promessas falhe, caso contrário, o processamento será interrompido.

E em vez de definir novas variáveis ​​a partir do argumento args , você deve ser capaz de usar spread () em vez de then () para todo tipo de trabalho incrível.

Gabriel
fonte
3

Simplesmente crie um objeto e extraia argumentos desse objeto.

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

Puxe argumentos de PromessaResolution.

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

fonte
2

O que quer que você retorne de uma promessa será embrulhado em uma promessa a ser desfeita no próximo .then()estágio.

Torna-se interessante quando você precisa retornar uma ou mais promessa (s) ao lado de um ou mais valores síncronos, como;

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

Nesses casos, seria essencial usar Promise.all()para obter p1e p2promessas desembrulhadas na próxima .then()fase, como

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

Você pode marcar Observable representado por Rxjs , permite retornar mais de um valor.

codelovesme
fonte
0

Basta retornar uma 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]));

}

swissmawi
fonte