Reestruturei meu código para promessas e construí uma cadeia longa e maravilhosa de promessas , consistindo em vários .then()
retornos de chamada. No final, desejo retornar algum valor composto e precisar acessar vários resultados de promessa intermediária . No entanto, os valores de resolução do meio da sequência não estão no escopo no último retorno de chamada, como os acesso?
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?
});
}
javascript
, é relevante em outro idioma. Eu só uso a resposta "quebrar a cadeia" em java e jdeferredRespostas:
Quebrar a corrente
Quando precisar acessar os valores intermediários em sua cadeia, você deve separá-la nas partes únicas necessárias. Em vez de anexar um retorno de chamada e de alguma forma tentar usar seu parâmetro várias vezes, anexe vários retornos à mesma promessa - sempre que precisar do valor do resultado. Não se esqueça, uma promessa representa (proxies) um valor futuro ! Depois de derivar uma promessa da outra em uma cadeia linear, use os combinadores de promessa que são fornecidos pela sua biblioteca para criar o valor do resultado.
Isso resultará em um fluxo de controle muito simples, composição clara de funcionalidades e, portanto, fácil modularização.
Em vez da desestruturação parâmetro na chamada de retorno após
Promise.all
que só se tornou disponível com ES6, em ES5 athen
chamada seria substituído por um método auxiliar bacana que foi fornecida por muitas bibliotecas promessa ( Q , Bluebird , quando , ...):.spread(function(resultA, resultB) { …
.O Bluebird também possui uma
join
função dedicada para substituir essa combinaçãoPromise.all
+spread
por uma construção mais simples (e mais eficiente):fonte
promiseA
epromiseB
são as funções (retorno de promessa) aqui.spread
era super útil nesse padrão. Para soluções mais modernas, consulte a resposta aceita. No entanto, eu já atualizei a resposta de passagem explícita e não há realmente nenhuma boa razão para não atualizá-la também.Harmonia do ECMAScript
Obviamente, esse problema também foi reconhecido pelos designers de idiomas. Eles fizeram muito trabalho e a proposta de funções assíncronas finalmente entrou em
ECMAScript 8
Você não precisa mais de uma única
then
chamada ou função de retorno de chamada, pois em uma função assíncrona (que retorna uma promessa ao ser chamada), basta aguardar a resolução direta das promessas. Ele também possui estruturas de controle arbitrárias, como condições, loops e cláusulas try-catch, mas por conveniência, não precisamos delas aqui:ECMAScript 6
Enquanto esperávamos o ES8, já usamos um tipo de sintaxe muito semelhante. O ES6 veio com funções geradoras , que permitem dividir a execução em partes com
yield
palavras-chave colocadas arbitrariamente . Essas fatias podem ser executadas uma após a outra, independentemente, de forma assíncrona - e é exatamente isso que fazemos quando queremos esperar por uma resolução de promessa antes de executar a próxima etapa.Existem bibliotecas dedicadas (como co ou task.js ), mas também muitas bibliotecas promissoras têm funções auxiliares ( Q , Bluebird , quando , ...) que executam essa execução passo-a-passo assíncrona quando você fornece a elas uma função geradora que produz promessas.
Isso funcionou no Node.js. desde a versão 4.0, também alguns navegadores (ou suas edições dev) suportaram a sintaxe do gerador relativamente cedo.
ECMAScript 5
No entanto, se você quiser / precisar ser compatível com versões anteriores, não poderá usá-las sem um transpiler. As funções do gerador e as funções assíncronas são suportadas pelas ferramentas atuais, consulte, por exemplo, a documentação do Babel nos geradores e funções assíncronas .
Além disso, também existem muitas outras linguagens de compilação em JS dedicadas a facilitar a programação assíncrona. Eles geralmente usam uma sintaxe semelhante a
await
(por exemplo, Iced CoffeeScript ), mas também existem outras que apresentam umado
notação semelhante a Haskell (por exemplo , LatteJs , monadic , PureScript ou LispyScript ).fonte
getExample
ainda é uma função que retorna uma promessa, funcionando exatamente como as funções das outras respostas, mas com uma sintaxe melhor. Você pode fazerawait
uma chamada em outraasync
função ou pode encadear.then()
seu resultado.steps.next().value.then(steps.next)...
mas isso não funcionou.Inspeção síncrona
Atribuindo valores de promessa para mais tarde necessários a variáveis e, em seguida, obtendo seu valor por meio de inspeção síncrona. O exemplo usa o
.value()
método bluebird, mas muitas bibliotecas fornecem um método semelhante.Isso pode ser usado para quantos valores você desejar:
fonte
Aninhamento (e) fechamentos
Usar closures para manter o escopo de variáveis (no nosso caso, os parâmetros da função de retorno de chamada de sucesso) é a solução JavaScript natural. Com promessas, podemos arbitrariamente aninhar e nivelar
.then()
retornos de chamada - eles são semanticamente equivalentes, exceto pelo escopo do interno.Claro, isso está construindo uma pirâmide de indentação. Se a indentação estiver ficando muito grande, você ainda poderá aplicar as ferramentas antigas para combater a pirâmide da desgraça : modularize, use funções nomeadas extras e achatar a cadeia de promessas assim que não precisar mais de uma variável.
Em teoria, você sempre pode evitar mais de dois níveis de aninhamento (explicitando todos os fechamentos); na prática, use quantos forem razoáveis.
Você também pode usar funções auxiliares para este tipo de aplicação parcial , como
_.partial
a partir Sublinhado / lodash ou o nativo.bind()
método , a redução ainda maior recuo:fonte
bind
função em Mônadas. Haskell fornece açúcar sintático (do-notation) para fazer com que pareça com sintaxe assíncrona / aguardada.Passagem explícita
Semelhante ao aninhamento dos retornos de chamada, essa técnica depende de fechamentos. No entanto, a cadeia permanece plana - em vez de passar apenas o último resultado, algum objeto de estado é passado para cada etapa. Esses objetos de estado acumulam os resultados das ações anteriores, entregando todos os valores que serão necessários mais tarde, mais o resultado da tarefa atual.
Aqui, essa pequena flecha
b => [resultA, b]
é a função que se fecharesultA
e passa uma matriz de ambos os resultados para a próxima etapa. Que usa a sintaxe de destruição de parâmetros para dividi-lo em variáveis únicas novamente.Antes que a desestruturação se tornasse disponível no ES6, um método bacana chamado
.spread()
era fornecido por muitas bibliotecas de promessas ( Q , Bluebird , quando ,…). É preciso usar uma função com vários parâmetros - um para cada elemento da matriz - como.spread(function(resultA, resultB) { …
.Obviamente, esse fechamento necessário aqui pode ser ainda mais simplificado por algumas funções auxiliares, por exemplo
Como alternativa, você pode empregar
Promise.all
para produzir a promessa para a matriz:E você pode não apenas usar matrizes, mas objetos arbitrariamente complexos. Por exemplo, com
_.extend
ouObject.assign
em uma função auxiliar diferente:Embora esse padrão garanta uma cadeia plana e objetos de estado explícito possam melhorar a clareza, ele se tornará tedioso para uma cadeia longa. Especialmente quando você precisa apenas do estado esporadicamente, ainda precisa passar por cada passo. Com essa interface fixa, os retornos de chamada únicos na cadeia são bastante acoplados e inflexíveis para alterações. Isso dificulta a realização de etapas únicas e os retornos de chamada não podem ser fornecidos diretamente de outros módulos - eles sempre precisam ser agrupados em código padrão que se preocupa com o estado. Funções auxiliares abstratas, como as descritas acima, podem aliviar um pouco a dor, mas sempre estará presente.
fonte
Promise.all
deve ser incentivada (ela não funcionará no ES6 quando a desestruturação a substituir e a troca de um.spread
para athen
fornece resultados inesperados às pessoas.) A partir do aumento - não sei por que você precisa usar o aumento - adicionar itens ao protótipo de promessa não é uma maneira aceitável de estender as promessas do ES6 de qualquer maneira, que deveriam ser estendidas com a subclasse (atualmente não suportada).Promise.all
"? Nenhum dos métodos nesta resposta será interrompido com o ES6. Mudar despread
para uma desestruturaçãothen
também não deve ter problemas. Re .prototype.augment: Eu sabia que alguém iria notar, eu só gostava de explorar possibilidades - editá-lo.return [x,y]; }).spread(...
vez dereturn Promise.all([x, y]); }).spread(...
que não mudaria ao trocar o spread pelo açúcar de desestruturação es6 e também não seria um caso estranho, onde as promessas tratam as matrizes retornadas de maneira diferente de tudo o resto.Estado contextual mutável
A solução trivial (mas deselegante e bastante propensa a erros) é usar apenas variáveis de escopo mais alto (às quais todos os retornos de chamada na cadeia têm acesso) e gravar valores de resultado para eles quando você os obtiver:
Em vez de muitas variáveis, pode-se também usar um objeto (inicialmente vazio), no qual os resultados são armazenados como propriedades criadas dinamicamente.
Esta solução tem várias desvantagens:
A biblioteca Bluebird incentiva o uso de um objeto que é passado adiante, usando seu
bind()
método para atribuir um objeto de contexto a uma cadeia de promessas. Ele estará acessível a partir de cada função de retorno de chamada por meio dathis
palavra - chave inutilizável . Enquanto as propriedades do objeto são mais propensas a erros de digitação não detectados do que variáveis, o padrão é bastante inteligente:Essa abordagem pode ser facilmente simulada em bibliotecas de promessas que não suportam .bind (embora de uma maneira um pouco mais detalhada e não possam ser usadas em uma expressão):
fonte
.bind()
é desnecessário para a prevenção de vazamento de memóriaUma rotação menos dura do "estado contextual mutável"
Usar um objeto com escopo local para coletar os resultados intermediários em uma cadeia de promessas é uma abordagem razoável para a pergunta que você fez. Considere o seguinte trecho:
fonte
Promise
construtor antipadrão !O nó 7.4 agora suporta chamadas assíncronas / em espera com o sinalizador de harmonia.
Tente o seguinte:
e execute o arquivo com:
node --harmony-async-await getExample.js
Simples como pode ser!
fonte
Hoje em dia, eu também tenho algumas perguntas como você. Por fim, encontro uma boa solução com a pergunta, é simples e bom de ler. Espero que isso possa ajudá-lo.
De acordo com o how-to-chain-javascript-promises
ok, vamos olhar o código:
fonte
.then
exigida, mas antes disso. Por exemplo,thirdPromise
acessando o resultado defirstPromise
.Outra resposta, usando a
babel-node
versão <6Usando
async - await
npm install -g [email protected]
example.js:
Então corra
babel-node example.js
e pronto!fonte
Não vou usar esse padrão no meu próprio código, pois não sou muito fã de usar variáveis globais. No entanto, em uma pitada, ele funcionará.
O usuário é um modelo de Mongoose promissificado.
fonte
globalVar
nada, apenas precisaUser.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });
?Outra resposta, usando o executor seqüencial nsynjs :
Atualização: exemplo de trabalho adicionado
fonte
Ao usar o bluebird, você pode usar o
.bind
método para compartilhar variáveis na cadeia de promessa:verifique este link para obter mais informações:
http://bluebirdjs.com/docs/api/promise.bind.html
fonte
maneira fácil: D
fonte
Eu acho que você pode usar o hash do RSVP.
Algo como o abaixo:
fonte
Promise.all
solução , apenas com um objeto em vez de uma matriz.Solução:
Você pode colocar valores intermediários no escopo em qualquer função posterior 'then' explicitamente, usando 'bind'. É uma solução agradável que não requer alteração no funcionamento do Promises e requer apenas uma ou duas linhas de código para propagar os valores, assim como os erros já são propagados.
Aqui está um exemplo completo:
Esta solução pode ser invocada da seguinte maneira:
(Nota: uma versão mais complexa e completa desta solução foi testada, mas não esta versão de exemplo, portanto, pode haver um erro.)
fonte
async
/await
ainda significa usar promessas. O que você pode abandonar são asthen
chamadas com retornos de chamada.O que eu aprendo sobre promessas é usá-lo apenas porque os valores de retorno evitam referenciá-los, se possível. A sintaxe assíncrona / aguardada é particularmente prática para isso. Hoje, todos os navegadores e nós mais recentes o suportam: https://caniuse.com/#feat=async-functions , é um comportamento simples e o código é como ler código síncrono, esquecer retornos de chamada ...
Nos casos em que preciso fazer referência a promessas, é quando a criação e a resolução ocorrem em locais independentes / não relacionados. Portanto, em vez de uma associação artificial e provavelmente um ouvinte de eventos apenas para resolver a promessa "distante", prefiro expor a promessa como adiada, que o código a seguir implementa em es5 válido
transpilados de um projeto datilografado meu:
https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31
Para casos mais complexos, costumo usar esses pequenos utilitários promissores, sem dependências testadas e digitadas. O p-map foi útil várias vezes. Acho que ele cobriu a maioria dos casos de uso:
https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=
fonte