Operações assíncronas em Javascript com Promises
Quando se fala em operações assíncronas, já vem aquele calafrio. Costuma ser algo doloroso de desenvolver. O motivo é que muitos desenvolvedores não entendem como funciona o seu fluxo de execução e por isso copiam as piores soluções da internet (famosos callbacks) para implementar esse comportamento.
Nesse artigo, vou tentar explicar de maneira simples e eficiente como implementar essas operações da melhor forma possível.
Assíncrono?
No javascript, executamos comandos em sequência, isto é, cada comando espera o outro concluir para poder executar (síncrono):
Em algumas situações, a operação pode demorar muito a ponto de não ser possível esperar o seu término (para não bloquear a interface com o usuário, por exemplo). Nessa situação, é necessário terminar as operações em tempo oportuno, sem prejuízo para o usuário final. Esse comportamento é o que chamamos de assíncrono:
Como tratar a resposta de uma operação assíncrona?
Em alguns casos isso não é necessário. Mas na maioria dos casos, precisaremos recuperar a informação retornada pelo procedimento assíncrono e realizar algum processamento. Por exemplo, se a nossa operação assíncrona for o acesso a um serviço web:
Da forma como foi implementado acima, as operações serão concluídas em background, mas nenhum tratamento poderá ser feito com os dados retornados, porque ninguém esperou para obter o resultado de cada operação. Para resolver o problema, você pode estar pensando: “é simples, só preciso retornar o resultado do fetch e armazená-lo numa variável para fazer o tratamento dos dados”:
Se o fetch não fosse uma operação assíncrona, seria impresso undefined no console. Como ele é, são impressos objetos chamados Promises. Esses objetos, como o nome já diz, são promesas de que o processamento será concluído em algum momento. É por isso que são impressas promises com status pending. Então, como eu consigo saber o momento em que uma promise “cumpriu a promessa”, i.e., foi resolvida? Isso pode ser feito pelo uso do then:
Veja que para recuperar o valor retornado pelas promises, eu preciso adicionar a atribuição dentro do escopo do then, pois é lá que a resposta (res) está preenchida. O problema é que as operações são assíncronas, então a atribuição não é feita antes da execução do console.log e por isso é impresso undefined. Para conseguir imprimir as respostas quando elas estiverem disponíveis, é necessário logá-las dentro do escopo do then:
É assim que se lida com operações assíncronas no javascript, utilizando promises e não funções callback.
E se eu precisar esperar o resultado da operação assíncrona?
No exemplo anterior não foi importante esperar a resposta do fetch antes de terminar a execução. O log foi exibido em momento oportuno, sem prejuízo para a lógica do programa. Mas e se eu precisasse de uma informação da operação assíncrona antes de chamar outra operação assíncrona? Um exemplo seria, dada uma todo, recuperar o seu usuário:
É possível resolver o problema encadeando chamadas do then de acordo com a sequencia de operações assíncronas desejada. Mas e se precisarmos retornar o resultado do encadeamento dessas operações? Vamos transformar esse código numa função reusável e retornar o usuário encontrado:
Você deve ter adivinhado que isso não iria dar certo. A variável usuario não vai receber o valor de forma síncrona, por isso a função retorna undefined. Para garantir que essa variável vai receber o valor antes da função terminar de executar, é necessário tornar a operação síncrona. Sim, isso pode ser feito em javascript com o uso do await:
O await só pode ser utilizado em operações assíncronas, por isso foi necessário adicionar o async na nossa função. Veja que utilizamos o await duas vezes, uma no encadeamento das operações que resultam na recuperação do usuário e outra na chamada a função assíncrona. Isso é necessário pois precisamos esperar a variável usuario ser inicializada, e a asyncfunction executar antes de imprimir o seu retorno.
💡 É importante não abusar do uso do await. Se você estiver usando o tempo todo, significa que a sua operação é naturalmente síncrona. O uso do await é mais comum quando usamos funções assíncronas nativas do javascript em operações síncronas da nossa aplicação.
Chamadas assíncronas dentro do forEach
Esse tópico é uma grande dor de cabeça para os desenvolvedores. Vamos pensar na situação em que eu precisaria chamar uma api rest para uma lista de valores. Naturalmente isso seria implementado utilizando um loop:
No código acima estamos apenas imprimindo, não estamos ainda recuperando os dados retornados. Vamos agora modificar para esperar todas as todos serem encontradas para retorná-las:
Não funciona! Mas fizemos tudo certinho, colocamos o await nos trechos assíncronos, deveria funcionar, correto? Para entender o que aconteceu, vamos observar a implementação do forEach do javascript:
Veja que se a função callback passada for assíncrona, o forEach não vai esperar ela concluir para retornar o valor, por isso chamadas assíncronas dentro do forEach não funcionam. Como resolvemos isso então? Novamente, “promises to the rescue”! Dessa vez, teremos várias promises e precisaremos retornar o valor final apenas quando todas elas executarem. Para fazer isso vamos utilizar o Promise.all:
Fizemos duas principais mudanças: utilizamos o map para retornar as promises de recuperação das todos dos usuários e adicionamos o Promise.all para retornar o resultado quando todas as promises forem resolvidas. Com essa estratégia podemos realizar várias chamadas assíncronas e aguardar o resultado de todas elas.
Conclusão
Nesse artigo foram discutidos os principais problemas relacionados a operações a assíncronas no javascript e a forma de resolvê-los utilizando promises. É importante deixar claro que os exemplos dados são ilustrativos, em muitos casos, pensar numa arquitetura assíncrona pode levar a decisões de implementação diferentes, que não dependam, por exemplo, do uso do await para forçar sincronicidade. Um exemplo bem comum é utilizar um modal que sumiria da tela apenas quando a operação assíncrona for resolvida, ao invés de forçar a sincronicidade no processo.
Espero que os exemplos dados aqui possam servir de orientação na elaboração de operações assíncronas utilizando javascript. Resumindo, abrace as promises e diga adeus as funções de callback!
Se você gosta do meu conteúdo, não deixe de conferir o meu canal do Youtube, onde falo sobre desenvolvimento de software. Espero te ver por lá! 😉