Promise com async / await ou com fetch / then?

douglasabnovato
7 min readDec 28, 2021

--

Conforme as aplicações vão ganhando complexidade, vamos nos deparando com situações que precisamos seguir com o programa funcionando mesmo quando há requisições ao backend ou o fluxo de código precisa de respostas de um processamento. Vamos aos detalhes.

Nesse artigo, precisaremos chegar ao final entendendo os termos promises, async / await, programação assíncrona, then, fetch, api rest. Então, ficamos atentos.

Aprenderemos sobre o que são Promises e como trabalhar com programação assíncrona em JavaScript utilizando .then() e também o método mais moderno async/await.

Na construção de aplicações web, precisamos acessar informações do backend, fazemos requisições a API Rest, ou lidamos com situações que chamamos funções para processar algum dado para completar a regra de negócio, assim há a necessidade de aguardar por dados para continuar a trabalhar as informações e construir a aplicação e exibir ao usuário.

Essa lógica recebe o nome de programação assíncrona, quando nosso código deve aguardar certo processamento que não está disponível no momento naquele ponto do fluxo tradicional do código.

Para uma comparação, um código síncrono é aquele de ocorre em sequência, uma instrução após a outra.

Código Assíncrono

Quando o código precisa aguardar para receber alguma informação que pode ser de uma API ou de um processamento externo, temos uma situação delicada a tratar.

Ao mesmo tempo que é preciso aguardar a requisição e resposta desse processamento externo, não podemos bloquear o funcionamento de todo o fluxo do nosso código.

O processamento assíncrono trata essa situação com as promises, ou seja, promessas. Quando enviamos uma requisição de dados a uma API, temos uma promessa de que estes dados irão chegar, mas enquanto isso não acontece, o sistema deve continuar rodando.

Caso algo aconteça que não aja a resposta da promessa realizada, então as promises trabalham nesse contexto.

Existem duas formas de se trabalhar com processamento assíncrono, as promises, em JavaScript: utilizando o método .then() ou as palavras-chave async / await.

Utilizando Promises com .then()

Vamos a um exemplo de API Rest, usando Fetch API do javascript para buscar dados e convertê-los para o formato JSON.

A documentação dessa API está no link a seguir.

Esta API funciona nativamente nos navegadores com alguns métodos que retorna por padrão uma promise que soluciona a requisição, tanto no caso de retorno com sucesso quanto no sem sucesso.

No código acima, a função getUser() recebe um id de usuário como parâmetro, para que seja passado para o endpoint REST fictício. O método fetch() recebe como parâmetro o endpoint e retorna uma Promise.

Promises têm um método chamado .then(), que recebe uma função callback e retorna um "objeto-promessa". Não é um retorno dos dados, é a promessa do retorno destes dados.

Podemos seguir com o código, considerando os dados recebidos pela promise pois o javascript vai aguardar a resolução da promise sem pausar o fluxo do programa.

A resposta da requisição pode ou não estar pronta no momento solicitado. É possível requisitar à promise que execute uma função callback, somente, quando o resultado estiver disponível, seja dados de uma API ou uma mensagem de erro caso algo tenha dado errado com a requisição.

No exemplo acima, ao iniciarmos uma cadeia de promessas, por exemplo para fazer uma requisição HTTP, enquanto a resposta está pendente, ela retorna um Promise object.

O objeto, por sua vez, define uma instância do método .then(). Ao invés de passar o retorno da função callback diretamente para a função inicial, ela é passada para .then(). Quando o resultado da requisição HTTP chega, o corpo da requisição é convertido para JSON e este valor convertido é passado para o próximo método .then().

A cadeia de funções fetch().then().then() não significa que há múltiplas funções callbacks sendo usadas com o mesmo objeto de resposta, e sim que cada instância de .then() retorna, por sua vez, um new Promise().

Toda a cadeia é lida de forma síncrona na primeira execução, e em seguida executada de forma assíncrona.

Capturando erros com Promises

O manejo de erros é importante em operações assíncronas. Quando o código é síncrono, pode lançar pelo menos uma exceção mesmo sem nenhum tipo de tratamento de erros. Porém, no assíncrono, exceções não tratadas passam sem aviso e fica muito mais difícil de debugar.

Não há como utilizar o try / catch quando usamos o .then(), pois a computação só será efetuada após o retorno do objeto-Promise. Então devemos passar funções que executem as alternativas, para o caso de sucesso ou falha da operação.

Por exemplo, repare o trecho de código a seguir.

Além do método .then() que recebe o objeto-Promise para ser resolvido, o método .catch() retorna no caso de rejeição da Promise. Além disso, o último método, .finally(), é chamado independente de sucesso ou falha da promessa e a função callback deste método é sempre executada por último e pode ser usada, por exemplo, para fechar uma conexão ou dar algum aviso de fim de carregamento.

Resolvendo diversas Promises

No caso de várias promessas que podem ser feitas paralelamente, por exemplo, alguns dados em endpoints REST diferentes, pode-se utilizar Promise.all.

Uma Promise pode estar “pendente”, pending, ou “resolvida”, settled. Os estados possíveis, uma vez que uma Promise está settled, são “concluída” , fulfilled, ou “rejeitada”, rejected.

Após passar de pending para settled e se definir como fulfilled ou rejected, a Promise não muda mais de estado.

Utilizando o async/await

O async/await trabalha com o código baseado em Promises, porém esconde as promessas para que a leitura seja mais fluída e simples de entender.

As palavras-chave async e await, implementadas a partir do ES2017, são uma sintaxe que simplifica a programação assíncrona, facilitando o fluxo de escrita e leitura do código, assim é possível escrever código que funciona de forma assíncrona, porém é lido e estruturado de forma síncrona.

Definindo uma função como async, podemos utilizar a palavra-chave await antes de qualquer expressão que retorne uma promessa.

Dessa forma, a execução da função externa, a função async, será pausada até que a Promise seja resolvida.

A palavra-chave await recebe uma Promise e a transforma em um valor de retorno, ou lança uma exceção em caso de erro. Quando utilizamos await, o JavaScript vai aguardar até que a Promise finalize.

Se for finalizada com sucesso, o termo utilizado é fulfilled, o valor obtido é retornado. Se a Promise for rejeitada, o termo utilizado é rejected, é retornado o erro lançado pela exceção.

Vamos a um exemplo de promise, lembrando que só é possível usar await em funções declaradas com a palavra-chave async:

Uma função declarada como async significa que o valor de retorno da função será, "por baixo dos panos", uma Promise. Se a Promise se resolver normalmente, o objeto-Promise retornará o valor.

Caso lance uma exceção, podemos usar o try / catch como estamos acostumados em programas síncronos.

Para executar a função getUser(), já que ela retorna uma Promise, pode-se usar await:

Lembrando que await só funciona se estiver dentro de outra função async. Caso não esteja, você ainda pode usar .then() normalmente:

Há diferenças entre .then() e async/await?

Em termos de sintaxe, o método .then() traz uma sintaxe que faz mais sentido quando utilizamos o JavaScript de forma funcional, enquanto o fluxo das declarações com async/await fazem sentido ao serem utilizadas em métodos de classes.

O async/await surgiu como uma opção mais "legível" ao .then(), mas é importante notar que estes métodos não são logicamente equivalentes.

O async/await faz o processamento de forma sequencial, simplifica a escrita e a interpretação do código, mas não é tão flexível e só funciona com uma Promise por vez.

As Promises com .then() são processadas em paralelo, o que faz com que este método seja mais rápido.

Como resolver esse tipo de caso, por exemplo, requisitando uma array de id de pedidos de determinado cliente de um comércio:

No caso acima, usamos Promise.all para fazer as requisições em paralelo, sem aguardar a anterior retornar para fazer a próxima.

Iterações com async/await

Mas e se precisarmos manejar várias Promises, mas não quisermos fazer isso em paralelo? Um exemplo clássico desta situação é acessar um banco de dados com milhares de registros.

Neste caso, não queremos que todas as requisições sejam feitas em paralelo, pois o excesso de requisições simultâneas pode causar problemas de performance e até derrubar o serviço.

Neste caso o async/await é mais indicado, pois vai resolver uma Promise por vez.

No caso acima, não queremos fazer todas as requisições ao banco de uma vez, e sim de forma sequencial.

Esses e outros detalhes podem ser encontrados na documentação oficial do javascript.

Outros códigos de exemplo e exercícios podem ser encontrados no portal w3schools.

Esse artigo ficou menor que os demais, mas a intenção é tratar situações mais específicas nesse tipo de tema técnico e prático. Tomara que tenha gostado, aplauda o artigo! Até o próximo artigo e ótimos códigos.

--

--