Menu

async/await em JavaScript: guia prático com exemplos

Como async/await funciona de verdade em JavaScript: funções assíncronas, await em promises, tratamento de erros com try/catch e execução paralela.

async/await é só Promise com outra roupagem

O async/await não é um novo modelo de concorrência. É só um açúcar sintático em cima das Promises que deixa você escrever código que parece sequencial, mesmo sendo assíncrono. A engrenagem é a mesma, só que bem mais agradável de ler.

Veja a mesma tarefa escrita das duas formas:

index.js
Output
Click Run to see the output here.

As duas funções retornam uma Promise e fazem exatamente a mesma coisa. A diferença é que a versão com async pode ser lida de cima para baixo, sem aquela corrente de .then — e é justamente aí que mora o charme.

async sinaliza que a função retorna uma Promise

Ao colocar async antes de uma function, de uma arrow function ou de um método, duas coisas acontecem:

  1. A função sempre retorna uma Promise. O que você passar no return vira o valor resolvido.
  2. Você libera o uso do await dentro dela.
index.js
Output
Click Run to see the output here.

Perceba que result não é a string em si — é uma promise que resolve para aquela string. Mesmo que greet não tenha nenhum await nem qualquer operação assíncrona, a palavra-chave async ainda envolve o valor de retorno em uma promise. E se a função lançar um erro, a promise é rejeitada.

await: pausando até a promise resolver

Dentro de uma função async, usar await umaPromise pausa a execução daquela função até que a promise resolva, e então devolve o valor resolvido. Caso a promise seja rejeitada, o await lança a exceção.

index.js
Output
Click Run to see the output here.

Preste atenção na ordem do output. O "started countdown" aparece antes do "2" — isso porque await só pausa a função async, não o resto do programa. O event loop continua rodando; a countdown só volta a executar quando cada promise de wait resolve.

Dá pra usar await em qualquer coisa parecida com uma promise. Até await 42 funciona — valores que não são promises são envolvidos automaticamente em Promise.resolve(42) e resolvidos na hora.

Tratamento de erro com try/catch no async/await

Com promises puras, você encadeia .catch(). Já com async/await, uma promise rejeitada vira uma exceção que você captura do jeito tradicional:

index.js
Output
Click Run to see the output here.

Um único try/catch cobre todos os await dentro dele. Falhas de rede, erros ao parsear JSON e os seus próprios throw caem todos no mesmo catch. É um ganho real em relação àquelas cadeias aninhadas de .then/.catch.

Só fique atento a um detalhe: o fetch só rejeita em erros de rede, não em respostas HTTP 4xx/5xx. Você mesmo precisa verificar res.ok e disparar o erro — um padrão que vai aparecer o tempo todo em código de verdade.

Evite usar await dentro de loop em JavaScript sem necessidade

Essa é a pegadinha mais comum quando o assunto é async/await. Usar await sequencial dentro de um loop faz cada iteração esperar a anterior terminar:

index.js
Output
Click Run to see the output here.

sequential leva cerca de 900ms. Já parallel leva uns 300ms. A regra prática é simples: se as tarefas não dependem do resultado umas das outras, dispare todas e depois use await Promise.all. Só faça await uma a uma quando a próxima chamada realmente precisar do resultado da anterior.

Para coleções, o idiomático é Promise.all(items.map(async (x) => ...)). Um for...of comum com await dentro roda em série — às vezes é o que você quer (para respeitar rate limit ou manter ordem), mas na maioria dos casos não é.

Misturando async/await com Promises puras

Você não precisa escolher um lado. Funções async retornam promises, e o await funciona com qualquer promise — então dá para misturar os dois à vontade:

index.js
Output
Click Run to see the output here.

Os dois estilos são intercambiáveis. Use await quando o código fluir melhor de cima para baixo; use .then quando quiser algo rápido e pontual ou quando estiver fora de um contexto async.

Top-level await (em ES Modules)

Antigamente, era obrigatório envolver o await dentro de uma função async, porque o await não podia aparecer no nível superior do script. Isso mudou: dentro de um ES module (um arquivo .mjs ou um <script type="module">), agora dá para usar await direto no topo do arquivo:

// em um módulo ES
const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
const user = await res.json();
console.log(user.name);

O await no nível superior (top level await) adia a finalização do módulo até que a promise aguardada seja resolvida — qualquer módulo que importar esse arquivo também vai esperar. É útil para carregar configurações e fazer imports dinâmicos, mas use com parcimônia: um await lento no topo trava todo mundo que importar aquele módulo.

Em arquivos CommonJS ou em scripts inline comuns, isso continua gerando SyntaxError. A saída clássica é usar uma função async autoinvocada:

index.js
Output
Click Run to see the output here.

Pegadinhas que pegam todo mundo

Um tour rápido pelas ciladas mais comuns com async/await:

  • Esquecer o async. Usar await dentro de uma função normal gera erro de sintaxe. A solução é adicionar async na função — ou chamar a função assíncrona usando .then.
  • Esquecer de dar await no resultado. const data = getJSON(url); te devolve uma promise, não o dado em si. Se você usar esse valor como se fosse a resposta pronta, vai acabar vendo [object Promise] aparecendo na saída.
  • Rejeições não tratadas. Uma função async disparada sem acompanhamento (doWork();) vai engolir os erros em silêncio, a não ser que você encadeie um .catch ou use await dentro de um try/catch.
  • forEach com callback async. array.forEach(async (x) => await something(x)) não espera nada — o forEach simplesmente ignora as promises retornadas. Prefira for...of com await, ou então Promise.all(array.map(...)).
index.js
Output
Click Run to see the output here.

Execute: "finalizado?" aparece antes de qualquer "concluído", porque broken retorna sem esperar. Já fixed aguarda tudo e só então imprime "finalizado!" por último.

Quando usar async/await

Use async/await por padrão sempre que seu código tiver mais de um passo assíncrono em sequência, ou quando precisar de tratamento de erro no estilo try/catch. Promises puras ainda fazem sentido em one-liners triviais, em código de biblioteca que só devolve uma promise sem precisar aguardar nada, ou quando você realmente precisa de combinadores como Promise.race ou um .finally() encadeado.

Bem aplicado, o async/await faz o código assíncrono parecer uma receita de bolo: faz isso, depois isso, depois aquilo. O event loop continua trabalhando nos bastidores — você só não precisa mais raciocinar em termos de callbacks.

A seguir: a API fetch

A maior parte dos exemplos aqui usou fetch como um substituto genérico para "alguma coisa assíncrona". Vale olhar com mais calma: como funcionam requisições e respostas, como lidar com JSON, como definir headers e por que o fetch não rejeita em erros HTTP. É o que veremos na próxima página.

Perguntas frequentes

O que async/await faz em JavaScript?

async/await é uma sintaxe para lidar com promises que permite escrever código assíncrono como se fosse síncrono. O async marca uma função como retornando uma promise, e o await pausa a execução dentro dessa função até a promise ser resolvida, devolvendo o valor resultante. Por baixo dos panos continua sendo tudo promise — só que bem mais legível.

Dá para usar await fora de uma função async?

No topo de um módulo ES, sim — é o chamado top-level await. Já dentro de funções comuns ou em scripts CommonJS, não: usar await fora de uma função async gera erro de sintaxe. A solução costuma ser envolver o trecho numa função async e chamá-la, ou converter o arquivo para módulo ES.

Como tratar erros com async/await?

Envolva as chamadas com await em um bloco try/catch. Qualquer promise rejeitada que você aguardar com await vira uma exceção que cai no catch. Para tarefas em segundo plano que você não aguarda, lembre-se de anexar um .catch() à promise retornada para não deixar rejeições sem tratamento.

O await trava o programa inteiro?

Não. O await só pausa a função async atual. O event loop continua rodando normalmente — timers disparam, outras tarefas assíncronas progridem e a UI segue responsiva. Quem chamou a função recebe uma promise pendente na hora e continua sua execução.

Aprenda a programar com o Coddy

COMEÇAR