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 amazingData
em 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 afterSomethingElse
aceitar 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.
javascript
promise
q
Der Hochstapler
fonte
fonte
Respostas:
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.spread
e 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 .
fonte
.spread()
como o Bluebird sendo mostrado nesta resposta relacionada: stackoverflow.com/a/22776850/1624862Promise.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).Promise.all
cumpre com uma série . EmPromise.all([a,b]).then((a, b) =>
b
éundefined
. É por isso que você precisa fazer, o.then(([a, b]) =>
que é uma tarefa de desestruturaçãovocê 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" })
fonte
function step2([server, data]) { …
- dessa forma, você também evita atribuir a globais implícitos. E você realmente deve usarreturn
orPromise.resolve
, não onew Promise
construtor em seus exemplos.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 }); }
fonte
then
dentro do outrothen
? Não é um anti-padrão ?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 }
fonte
É 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.
fonte
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
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 obterp1
ep2
promessas desembrulhadas na próxima.then()
fase, comoPromise.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 */);
fonte
Você pode marcar Observable representado por Rxjs , permite retornar mais de um valor.
fonte
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]));
}
fonte