As Promises resolveram o Callback Hell. Mas encadear muitos .then() ainda pode ficar confuso quando há lógica condicional, loops ou múltiplas variáveis que precisam ser compartilhadas entre etapas.
O async/await, introduzido no ES2017, é açúcar sintático em cima das Promises — ele não substitui as Promises, apenas fornece uma sintaxe mais limpa para trabalhar com elas. O resultado é código assíncrono que se lê como código síncrono, sem perder nenhum dos benefícios das Promises.
A palavra-chave async
async transforma uma função em uma função assíncrona. Uma função async sempre retorna uma Promise, mesmo que você não use return explicitamente ou retorne um valor simples:
// Função síncrona comum
function somar(a, b) {
return a + b;
}
console.log(somar(2, 3)); // 5
// Função assíncrona — retorna uma Promise
async function somarAsync(a, b) {
return a + b;
}
somarAsync(2, 3).then(resultado => console.log(resultado)); // 5
// Também funciona com arrow functions
const multiplicar = async (a, b) => a * b;
A palavra-chave await
await só pode ser usado dentro de funções async. Ele pausa a execução da função até que a Promise seja resolvida — e retorna o valor resolvido:
function esperar(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function exemplo() {
console.log("Início");
await esperar(2000); // pausa aqui por 2 segundos
console.log("2 segundos depois");
await esperar(1000); // pausa mais 1 segundo
console.log("Mais 1 segundo depois");
}
exemplo();
console.log("Este executa imediatamente — não espera a função async");
// Saída:
// Início
// Este executa imediatamente — não espera a função async
// (2 segundos)
// 2 segundos depois
// (1 segundo)
// Mais 1 segundo depois
O await pausa somente a função async atual — o restante do programa continua normalmente.
Comparando Promises com async/await
O mesmo código escrito das duas formas:
function buscarUsuario(id) {
return new Promise(resolve =>
setTimeout(() => resolve({ id, nome: "Ana", plano: "premium" }), 800)
);
}
function buscarPedidos(usuarioId) {
return new Promise(resolve =>
setTimeout(() => resolve([{ id: 101 }, { id: 102 }]), 600)
);
}
// ── Com Promises e .then() ──────────────────
function carregarDadosPromise() {
return buscarUsuario(1)
.then(usuario => {
console.log(`Usuário: ${usuario.nome}`);
return buscarPedidos(usuario.id);
})
.then(pedidos => {
console.log(`Pedidos: ${pedidos.length}`);
})
.catch(erro => console.error(erro.message));
}
// ── Com async/await ─────────────────────────
async function carregarDadosAsync() {
try {
const usuario = await buscarUsuario(1);
console.log(`Usuário: ${usuario.nome}`);
const pedidos = await buscarPedidos(usuario.id);
console.log(`Pedidos: ${pedidos.length}`);
} catch (erro) {
console.error(erro.message);
}
}
A versão com async/await é mais legível — especialmente quando há lógica condicional entre as etapas.
Tratamento de erros com try/catch
Com async/await, o tratamento de erros volta a usar o familiar try/catch:
async function processarPedido(usuarioId, produtoId) {
try {
// Cada await pode lançar um erro se a Promise rejeitar
const usuario = await buscarUsuario(usuarioId);
const produto = await buscarProduto(produtoId);
if (produto.estoque === 0) {
throw new Error(`Produto "${produto.nome}" sem estoque.`);
}
const pedido = await criarPedido(usuario.id, produto.id);
console.log(`✅ Pedido #${pedido.id} criado com sucesso!`);
return pedido;
} catch (erro) {
console.error(`❌ Erro ao processar pedido: ${erro.message}`);
throw erro; // repropaga se necessário
} finally {
console.log("Processo de pedido finalizado.");
}
}
Lógica condicional — onde async/await brilha
O ponto onde async/await supera claramente o encadeamento de .then():
async function checkout(carrinho, cupom) {
const usuario = await buscarUsuarioLogado();
// Lógica condicional — muito mais clara que em .then()
if (!usuario) {
throw new Error("Usuário não autenticado.");
}
const itensVerificados = await verificarEstoque(carrinho);
let total = itensVerificados.reduce((acc, item) => acc + item.preco * item.quantidade, 0);
if (cupom) {
try {
const desconto = await validarCupom(cupom, total);
total = total - desconto;
console.log(`Cupom aplicado! Desconto: R$ ${desconto}`);
} catch {
console.warn("Cupom inválido ou expirado. Continuando sem desconto.");
}
}
const frete = await calcularFrete(usuario.cep);
total += frete.valor;
console.log(`Total com frete: R$ ${total.toFixed(2)}`);
console.log(`Previsão de entrega: ${frete.prazo}`);
const pedido = await confirmarPedido({ itens: itensVerificados, total, usuario });
return pedido;
}
Tente reescrever isso com .then() encadeados e você vai ver o quanto async/await ajuda.
Loops assíncronos
Async/await permite usar estruturas de controle normais com código assíncrono:
const ids = [1, 2, 3, 4, 5];
// ── Sequencial — aguarda cada um antes do próximo
async function carregarSequencial() {
console.log("Carregando em sequência...");
const resultados = [];
for (const id of ids) {
const usuario = await buscarUsuario(id); // aguarda antes de continuar
resultados.push(usuario);
console.log(`Carregado: ${usuario.nome}`);
}
return resultados;
}
// Tempo total: soma dos tempos individuais (~5s se cada um levar 1s)
// ── Paralelo — inicia todos ao mesmo tempo
async function carregarParalelo() {
console.log("Carregando em paralelo...");
const promises = ids.map(id => buscarUsuario(id));
const resultados = await Promise.all(promises);
resultados.forEach(u => console.log(`Carregado: ${u.nome}`));
return resultados;
}
// Tempo total: tempo do mais lento (~1s)
Use sequencial quando cada operação depende da anterior. Use paralelo (Promise.all) quando as operações são independentes.
async/await com Promise.all
Você pode — e deve — combinar async/await com os métodos de Promise:
async function carregarDashboard(usuarioId) {
// Inicia tudo em paralelo — eficiente
const [usuario, pedidos, notificacoes, relatorio] = await Promise.all([
buscarUsuario(usuarioId),
buscarPedidos(usuarioId),
buscarNotificacoes(usuarioId),
gerarRelatorio(usuarioId),
]);
return { usuario, pedidos, notificacoes, relatorio };
}
// Usando
async function main() {
try {
const dados = await carregarDashboard(42);
console.log(`Bem-vindo, ${dados.usuario.nome}!`);
console.log(`${dados.pedidos.length} pedidos encontrados.`);
console.log(`${dados.notificacoes.length} notificações.`);
} catch (erro) {
console.error("Erro ao carregar dashboard:", erro.message);
}
}
main();
Top-level await
No passado, await só podia ser usado dentro de funções async. Em módulos JavaScript modernos (.mjs ou com "type": "module" no package.json), você pode usar await diretamente no nível do módulo:
// Em um módulo ES moderno — sem precisar de função async
const dados = await fetch("https://api.exemplo.com/dados");
const json = await dados.json();
console.log(json);
Isso é muito útil em scripts de configuração e no Node.js moderno.
Erro comum — await em forEach
Um dos erros mais frequentes com async/await: usar await dentro de forEach não funciona como esperado:
const ids = [1, 2, 3];
// ❌ ERRADO — forEach não aguarda os awaits
async function errado() {
ids.forEach(async (id) => {
const usuario = await buscarUsuario(id);
console.log(usuario.nome); // a ordem é imprevisível
});
console.log("Terminou?"); // executa ANTES dos awaits!
}
// ✅ CORRETO — use for...of para código sequencial
async function correto() {
for (const id of ids) {
const usuario = await buscarUsuario(id);
console.log(usuario.nome); // ordem garantida
}
console.log("Terminou!"); // executa DEPOIS de todos
}
// ✅ CORRETO — use Promise.all para paralelo
async function paraleloCorreto() {
const usuarios = await Promise.all(ids.map(id => buscarUsuario(id)));
usuarios.forEach(u => console.log(u.nome));
console.log("Terminou!");
}
Padrões avançados
Retry automático — tentar novamente em caso de falha:
async function comRetry(funcao, tentativas = 3, delay = 1000) {
for (let i = 1; i <= tentativas; i++) {
try {
return await funcao();
} catch (erro) {
console.warn(`Tentativa ${i}/${tentativas} falhou: ${erro.message}`);
if (i === tentativas) throw erro; // última tentativa — propaga o erro
await new Promise(resolve => setTimeout(resolve, delay * i)); // espera antes de tentar de novo
}
}
}
// Uso
const dados = await comRetry(
() => buscarDadosDaAPI(),
3, // 3 tentativas
500, // 500ms, 1000ms, 1500ms entre tentativas
);
Timeout em requisições:
async function comTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout após ${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}
// Uso
try {
const dados = await comTimeout(buscarDadosDaAPI(), 5000);
console.log(dados);
} catch (erro) {
console.error(erro.message); // "Timeout após 5000ms"
}
Exemplo completo — sistema de autenticação
// Simulando serviços
const authService = {
async validarCredenciais(email, senha) {
await new Promise(r => setTimeout(r, 800));
if (email === "admin@email.com" && senha === "senha123") {
return { token: "jwt_token_abc123", expiraEm: 3600 };
}
throw new Error("Credenciais inválidas.");
},
async obterPerfil(token) {
await new Promise(r => setTimeout(r, 500));
if (!token) throw new Error("Token ausente.");
return {
id: 1,
nome: "Administrador",
email: "admin@email.com",
permissoes: ["ler", "escrever", "deletar"],
};
},
async registrarAcesso(userId) {
await new Promise(r => setTimeout(r, 200));
const agora = new Date().toLocaleString("pt-BR");
return { userId, acessoEm: agora, ip: "192.168.1.1" };
},
};
// Função de login completa
async function login(email, senha) {
console.log("🔐 Iniciando autenticação...");
try {
// Passo 1 — valida credenciais
const auth = await authService.validarCredenciais(email, senha);
console.log(`✅ Credenciais válidas. Token expira em ${auth.expiraEm}s`);
// Passo 2 e 3 — busca perfil e registra acesso em paralelo
const [perfil, registro] = await Promise.all([
authService.obterPerfil(auth.token),
authService.registrarAcesso(1),
]);
console.log(`👤 Bem-vindo, ${perfil.nome}!`);
console.log(`🔑 Permissões: ${perfil.permissoes.join(", ")}`);
console.log(`📋 Acesso registrado em: ${registro.acessoEm}`);
return { auth, perfil, registro };
} catch (erro) {
console.error(`❌ Falha no login: ${erro.message}`);
throw erro;
}
}
async function main() {
// Login bem-sucedido
await login("admin@email.com", "senha123");
console.log("\n--- Tentativa com credenciais erradas ---\n");
// Login com falha
await login("hacker@email.com", "errada").catch(() => {
console.log("Redirecionando para tela de erro...");
});
}
main();
Resumo — quando usar cada abordagem
| Situação | Use |
|---|---|
| Código simples e linear | async/await |
| Operações em paralelo | async/await + Promise.all |
| Encadeamento simples | .then() ou async/await |
| Eventos que ocorrem várias vezes | Callbacks |
Precisa de .race(), .any(), .allSettled() |
Combine com await |
| Loop que precisa ser sequencial | for...of com await |
| Loop paralelo | Promise.all + map com await |
Tarefa para você
Construa um sistema de importação de dados em lotes com async/await:
// Contexto: você tem 50 usuários para importar para um banco de dados.
// Importar todos de uma vez pode sobrecarregar o servidor.
// Importe em lotes de 10, com 500ms de pausa entre os lotes.
async function importarEmLotes(usuarios, tamanhoDeLote = 10, pausaMs = 500) {
// 1. Divida o array em lotes de 'tamanhoDeLote'
// 2. Para cada lote, importe todos em paralelo (Promise.all)
// 3. Aguarde 'pausaMs' antes do próximo lote
// 4. Exiba o progresso: "Lote 1/5 concluído (10/50 usuários)"
// 5. Retorne um relatório final: { sucesso, falha, total }
}
// Simule a função de importar um usuário:
async function importarUsuario(usuario) {
await new Promise(r => setTimeout(r, Math.random() * 300 + 100));
if (Math.random() < 0.1) throw new Error(`Falha ao importar ${usuario.nome}`);
return { id: Math.random(), ...usuario };
}
Conclusão
Neste artigo você aprendeu:
- O que
asyncfaz a uma função - Como
awaitpausa a execução sem bloquear a thread - Como async/await melhora legibilidade em relação ao
.then() - Tratamento de erros com
try/catch/finally - Como usar lógica condicional e loops com código assíncrono
- A combinação poderosa de
async/awaitcomPromise.all - O erro clássico do
awaitdentro doforEach - Padrões avançados: retry e timeout
- Quando usar cada abordagem
No próximo artigo colocamos tudo isso em prática com a Fetch API — buscando dados reais da internet em APIs públicas.
📚 Fontes e Referências
- MDN Web Docs — async function: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Statements/async_function
- MDN Web Docs — await: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Operators/await
- MDN Web Docs — Top-level await: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await
- JavaScript.info — Async/await: https://javascript.info/async-await
- JavaScript.info — Promise API: https://javascript.info/promise-api
- You Don't Know JS: ES6 & Beyond — Kyle Simpson: https://github.com/getify/You-Dont-Know-JS
- Eloquent JavaScript, Cap. 11 — Asynchronous Programming: https://eloquentjavascript.net/11_async.html
- Google Developers — JavaScript Promises: https://web.dev/promises