Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

async e await em JavaScript: como usar na prática

async devolve Promise, await só vive dentro dela, e trocar série por paralelo corta o tempo em três — com medições reais e o erro do forEach que não espera.

Rodolfo Mori6 min de leitura

async declara que uma função sempre devolverá uma Promise; await suspende a continuação daquela função até a Promise observada terminar. Juntos, eles deixam o fluxo assíncrono legível em sequência: uma linha abaixo da outra, resultado em variável e falha tratada com try/catch.

Pense no recibo de uma lavanderia. Você entrega a roupa e recebe um comprovante de algo que será concluído depois — a Promise. await é esperar aquele pedido ficar pronto antes de seguir para a próxima etapa que depende dele. A loja inteira não para enquanto você espera; no JavaScript, o event loop também pode continuar processando outras tarefas. Quem fica suspensa é somente a execução da função async naquele ponto.

Duas regras conduzem o restante da lição: uma função async sempre devolve uma Promise, mesmo quando retorna um número, e await só organiza o fluxo onde foi escrito. Operações independentes ainda precisam de Promise.all para começar juntas.

async transforma o retorno em Promise

Repare que o return devolve um objeto comum, e mesmo assim o que sai é uma Promise:

js
async function totalDoPedido(id) {
  return { id, total: 479.7 };
}

const resultado = totalDoPedido(8241);
console.log(resultado);
console.log(resultado instanceof Promise);

resultado.then((pedido) => console.log('resolvida com', pedido));
Promise { { id: 8241, total: 479.7 } } true resolvida com { id: 8241, total: 479.7 }

Consequência prática: quem chama uma função async precisa de await ou de then. Esquecer é o erro mais frequente da lição, e ele aparece no fim deste texto com o stack trace real.

Do lado do erro, o espelho é o mesmo: um throw dentro de uma função async não estoura na hora — ele vira uma Promise rejeitada, exatamente como o reject de Promise em JavaScript.

Onde o await pode morar

Dentro de função async, ou direto no corpo de um módulo ES. Fora disso, é erro de sintaxe — o arquivo nem chega a executar. Em CommonJS:

js
const buscarPedido = (id) => Promise.resolve({ id, total: 479.7 });

function mostrarPedido(id) {
  const pedido = await buscarPedido(id);
  console.log(pedido.total);
}

mostrarPedido(8241);
/private/tmp/loja-async/await-funcao-comum.cjs:4 const pedido = await buscarPedido(id); ^^^^^ SyntaxError: await is only valid in async functions and the top level bodies of modules at wrapSafe (node:internal/modules/cjs/loader:1787:18) at Module._compile (node:internal/modules/cjs/loader:1828:20) at Object..js (node:internal/modules/cjs/loader:1985:10) at Module.load (node:internal/modules/cjs/loader:1577:32) Node.js v24.16.0

A mensagem é literal e diz as duas saídas possíveis: marque a função com async, ou mova o await para o topo de um módulo. O mesmo arquivo salvo como .mjs dá uma mensagem diferente e mais críptica — SyntaxError: Unexpected reserved word — porque em módulo o await no topo é válido e o parser quebra só quando ele aparece dentro de uma função comum.

No topo de um módulo ES, funciona sem embrulho nenhum:

js
const buscarPedido = (id) =>
  new Promise((resolve) => setTimeout(() => resolve({ id, cliente: 'Ana', total: 479.7 }), 200));

const pedido = await buscarPedido(8241);
console.log(`${pedido.cliente}: R$ ${pedido.total.toFixed(2)}`);
Ana: R$ 479.70

Série contra paralelo: o mesmo código, três vezes mais lento

Três consultas independentes de 300ms cada, simuladas com um setTimeout para o tempo ficar previsível:

js
const espera = (ms, valor) => new Promise((resolve) => setTimeout(() => resolve(valor), ms));

const buscarCliente = () => espera(300, { nome: 'Ana' });
const buscarEstoque = () => espera(300, { disponivel: true });
const buscarFrete = () => espera(300, { valor: 24.9 });

Escritas uma embaixo da outra, com await em cada linha:

js
const inicio = Date.now();

const cliente = await buscarCliente();
const estoque = await buscarEstoque();
const frete = await buscarFrete();

console.log(cliente.nome, estoque.disponivel, frete.valor);
console.log('em série:', Date.now() - inicio, 'ms');
Ana true 24.9 em série: 905 ms

905ms para três coisas que não dependem uma da outra. Cada await só solta a próxima linha quando a anterior termina. Disparando as três antes de esperar:

js
const inicio = Date.now();

const [cliente, estoque, frete] = await Promise.all([
  buscarCliente(),
  buscarEstoque(),
  buscarFrete(),
]);

console.log(cliente.nome, estoque.disponivel, frete.valor);
console.log('em paralelo:', Date.now() - inicio, 'ms');
Ana true 24.9 em paralelo: 303 ms

De 905ms para 303ms, sem cache, sem otimizar consulta, sem tocar no servidor: só mudando quando as chamadas começam. A regra é curta: await em sequência serve para dependência; se a segunda chamada não usa o resultado da primeira, ela não deveria estar esperando.

try/catch volta a funcionar

Com await, uma Promise rejeitada lança um erro de verdade — e o try/catch de sempre pega:

js
const cobrar = (valor) =>
  new Promise((_, reject) =>
    setTimeout(() => reject(new Error('cartão recusado pela operadora')), 200),
  );

async function finalizarPedido(id, valor) {
  try {
    const recibo = await cobrar(valor);
    console.log('pago:', recibo);
    return recibo;
  } catch (e) {
    console.log('falhou:', e.message);
    return null;
  } finally {
    console.log(`pedido ${id}: fim da tentativa`);
  }
}

const recibo = await finalizarPedido(8241, 479.7);
console.log('recibo =', recibo);
falhou: cartão recusado pela operadora pedido 8241: fim da tentativa recibo = null

Note o return null dentro do catch: a função tratou a falha e devolveu um valor normal, então a Promise dela fica resolvida, não rejeitada. Se você quer que quem chamou também saiba do problema, o catch precisa relançar com throw.

await dentro de forEach não espera nada

Este é o bug que mais chega em revisão de código. Parece certo, roda sem erro e faz a coisa errada:

js
const baixarEstoque = (item) => espera(200, `${item} baixado`);
const itens = ['Teclado mecânico', 'Mouse sem fio', 'Cadeira gamer'];

console.log('inicio');

itens.forEach(async (item) => {
  const r = await baixarEstoque(item);
  console.log(r);
});

console.log('todos os itens processados?');
inicio todos os itens processados? Teclado mecânico baixado Mouse sem fio baixado Cadeira gamer baixado

A mensagem final saiu antes das baixas de estoque. O motivo é mecânico: o forEach chama a função, recebe uma Promise de volta e joga fora — ele não tem como esperar o que não olha. Se a linha seguinte fosse “fechar o pedido”, o pedido fecharia com o estoque ainda intacto.

Com for...of, o await pausa a função inteira a cada volta:

js
console.log('inicio');

for (const item of itens) {
  console.log(await baixarEstoque(item));
}

console.log('todos os itens processados');
inicio Teclado mecânico baixado Mouse sem fio baixado Cadeira gamer baixado todos os itens processados

E quando a ordem não importa, map + Promise.all faz tudo junto:

js
const inicio = Date.now();

const resultados = await Promise.all(itens.map(baixarEstoque));

console.log(resultados);
console.log('tudo em', Date.now() - inicio, 'ms');
[ 'Teclado mecânico baixado', 'Mouse sem fio baixado', 'Cadeira gamer baixado' ] tudo em 203 ms

600ms em série contra 203ms em paralelo, com o array de resultados na mesma ordem do array de entrada. Os detalhes desse comportamento estão em Promise.all, allSettled e race.

O erro comum: o await esquecido

Você marcou a função como async, envolveu em try/catch e ainda assim o erro escapa:

js
async function buscarPedido(id) {
  if (id !== 8241) throw new Error(`pedido ${id} não existe`);
  return { id, total: 479.7 };
}

function mostrar(id) {
  try {
    const pedido = buscarPedido(id); // faltou o await
    console.log('total:', pedido.total);
  } catch (e) {
    console.log('o catch nunca roda:', e.message);
  }
}

mostrar(9999);
console.log('fim do mostrar');
total: undefined fim do mostrar file:///private/tmp/loja-async/await-erro-async.mjs:2 if (id !== 8241) throw new Error(`pedido ${id} não existe`); ^ Error: pedido 9999 não existe at buscarPedido (file:///private/tmp/loja-async/await-erro-async.mjs:2:26) at mostrar (file:///private/tmp/loja-async/await-erro-async.mjs:8:20) at file:///private/tmp/loja-async/await-erro-async.mjs:15:1 Node.js v24.16.0

Três sintomas de uma causa só. pedido era a Promise, então pedido.total deu undefined. O catch não rodou, porque no instante em que o try terminou ainda não havia erro nenhum. E o processo morreu depois de fim do mostrar, com a rejeição não tratada.

Escolhendo entre as três formas

situação forma por quê
o passo 2 usa o resultado do passo 1 await em sequência não há o que paralelizar
chamadas independentes, precisa de todas Promise.all tempo do mais lento, não da soma
lista grande contra API com limite for...of com await controla o ritmo das requisições
precisa de todos os resultados, mesmo com falha Promise.allSettled uma falha não derruba as outras

Uma última recomendação de estilo: prefira async/await no código que você escreve e deixe then para casos pontuais de composição. Misturar os dois na mesma função — um await aqui, um .then() ali — é a receita mais confiável para perder o fio do que espera o quê.

Meça você mesmo: crie uma função esperar(ms) que resolve após um setTimeout. Rode três esperas de 100 ms primeiro em sequência e depois com Promise.all, medindo com performance.now(). A primeira deve ficar perto de 300 ms; a segunda, perto de 100 ms. Se os tempos forem iguais, confira se você criou as três Promises antes de esperá-las em conjunto.

O próximo passo da trilha de JavaScript é a rede de verdade: como consumir uma API com fetch, onde todo esse ferramental encontra HTTP, status code e CORS.

  • async
  • await
  • promise
  • assincrono
  • promise all

Perguntas frequentes

async/await substitui Promise?
Não substitui, escreve por cima. Uma função async devolve Promise e o await espera Promise. Você continua precisando de Promise.all para paralelizar e de catch quando o resultado da função vai para outra camada.
Posso usar await sem async no arquivo?
Só no corpo de um módulo ES — arquivo .mjs, ou .js num projeto com "type":"module". Em CommonJS, ou dentro de qualquer função não marcada com async, o arquivo nem chega a rodar: é erro de sintaxe.
Quando devo usar for...of em vez de Promise.all?
Quando cada passo depende do anterior, quando a API tem limite de requisições por segundo ou quando a ordem de escrita importa. Para operações independentes, Promise.all é sempre mais rápido.
Por que o try/catch não pegou o erro da minha função async?
Quase sempre por falta de await. Sem ele, a função devolve a Promise e o bloco try termina antes de a rejeição acontecer — o erro vira rejeição não tratada e cai fora do seu catch.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — async function — developer.mozilla.org
  2. MDN — await — developer.mozilla.org

Continue por aqui