Novidades

Async/await vs Promises: qual usar em JavaScript

ResumoAsync/await e Promises são duas abordagens para código assíncrono em JavaScript. Async/await oferece sintaxe mais legível e sequencial, facilitando o tratamento de erros com try/catch. Promises fornecem maior flexibilidade para operações paralelas com Promise.all. A escolha depende do cenário: async/await para fluxos lineares e Promises para concorrência complexa.

Async/await e Promises são duas formas de lidar com código assíncrono em JavaScript. Este guia compara os dois lados em critérios como legibilidade, tratamento de erros e performance, e revela qual escolher em cada cenário.

Zeca Maranhão
Async/await vs Promises: qual usar em JavaScript

Async/await vs Promises: qual usar em JavaScript — Foto: Reprodução / Blog Sem Juízo

No JavaScript moderno, código assíncrono é inevitável. Você está ali, escrevendo uma consulta a uma API, um acesso a banco de dados, ou uma leitura de arquivo, e de repente se depara com o dilema: usar Promises com .then() ou a sintaxe async/await? Ambas resolvem o mesmo problema, o famigerado callback hell, mas cada uma tem seu palco. E o palco errado pode transformar seu código em um novelo de lã. Este comparativo coloca frente a frente async/await e Promises em seis critérios essenciais, para que você saiba exatamente qual chamar para o jogo.

Legibilidade e facilidade de escrita

Promises com .then() criam uma cadeia de funções que, quando bem escritas, é quase poética. Mas quando o encadeamento passa de três ou quatro níveis, a indentação começa a dançar e o código fica parecendo uma escada. Async/await, por outro lado, faz o código assíncrono parecer síncrono. Você escreve como se estivesse lendo de cima para baixo, sem aninhamento.

// Promises fetchUser(id) .then(user => fetchPosts(user.id)) .then(posts => renderPosts(posts)) .catch(err => console.error(err));

// async/await try { const user = await fetchUser(id); const posts = await fetchPosts(user.id); renderPosts(posts); } catch (err) { console.error(err); }

O exemplo acima mostra a diferença: async/await reduz a poluição visual. Para quem está começando ou mantém código legado, a leitura linear é um alívio. Mas atenção: async/await não é uma nova tecnologia, é apenas uma forma diferente de escrever Promises. Por baixo dos panos, o motor JavaScript transforma async/await em Promises.

Tratamento de erros

Promises tratam erros com .catch() no final da cadeia. Isso funciona bem para erros que ocorrem em qualquer ponto da sequência, mas pode engolir exceções se você não tomar cuidado, um .catch() no lugar errado e o erro some no vácuo. Async/await usa try/catch, o mesmo bloco que você já usa em código síncrono. A vantagem é que você pode tratar erros específicos em cada await, ou usar um único catch para todos.

// Promise com .catch() genérico fetchData() .then(process) .catch(err => { if (err instanceof NetworkError) { / trata rede / } else { / trata genérico / } });

// async/await com try/catch específico try { const data = await fetchData(); await process(data); } catch (err) { if (err instanceof NetworkError) { / trata rede / } }

A pegadinha: se você esquecer de colocar um try/catch em uma função async, o erro vira uma Promise rejeitada não tratada, e pode derrubar o processo no Node.js. Já com Promises, o .catch() é mais explícito, e ferramentas como ESLint podem alertar sobre promessas não tratadas.

Concorrência e execução paralela

Aqui as Promises mostram sua força. Com Promise.all(), você dispara várias operações ao mesmo tempo e aguarda todas terminarem. Com Promise.race(), você pega o resultado da primeira que resolver. Async/await, por si só, executa uma operação de cada vez, a menos que você combine com Promise.all().

// Promises paralelas const [user, posts] = await Promise.all([ fetchUser(id), fetchPosts(id) ]);

// async/await sequencial (mais lento) const user = await fetchUser(id); const posts = await fetchPosts(id);

Se você precisa buscar dados independentes simultaneamente, Promise.all() com async/await é a combinação vencedora. Mas cuidado: se uma Promise rejeitar, Promise.all() rejeita tudo, para cenários onde você quer ignorar falhas, Promise.allSettled() é o caminho.

Depuração e stack traces

Async/await ganha pontos aqui. Quando uma exceção ocorre dentro de uma função async, o stack trace aponta para a linha exata do erro, como se fosse código síncrono. Com Promises encadeadas, o stack trace pode ser uma sopa de letrinhas, cada .then() adiciona uma camada na pilha, e identificar a origem do erro vira caça ao tesouro. Em engines modernos (V8, SpiderMonkey), os stack traces de Promises melhoraram, mas async/await ainda é mais amigável para o desenvolvedor que está debugando.

Performance

Teoricamente, Promises são ligeiramente mais rápidas que async/await, porque async/await adiciona uma camada extra de abstração e gerenciamento de estado no motor. Na prática, a diferença é irrelevante para 99% dos casos, estamos falando de microssegundos por operação. O gargalo do seu código não será a escolha entre async/await e Promises, mas sim as operações I/O (rede, disco, banco). Se você está otimizando um loop que executa milhares de Promises, talvez valha a pena usar Promises diretamente. Para o resto, escolha pela legibilidade.

Suporte e maturidade

Promises existem desde o ES6 (2015) e são suportadas em todos os ambientes modernos, incluindo Node.js a partir da versão 4. Async/await chegou com o ES8 (2017) e hoje é suportado em Node.js 8+ e todos os navegadores atuais. Se você precisa suportar ambientes muito antigos (IE11, Node.js 6), Promises com polyfill ainda são a aposta segura. Mas, convenhamos, em 2025, async/await já é o padrão de facto. A não ser que você esteja mantendo um projeto legacy com restrições de runtime, async/await é a escolha óbvia.

Tabela comparativa

| Critério | Promises | Async/await | |---|---|---| | Legibilidade | Boa para cadeias curtas; vira escada com muitos níveis | Excelente; código linear e intuitivo | | Tratamento de erros | .catch() no final; fácil de esquecer | try/catch familiar; risco de Promise não tratada | | Concorrência | Nativa com Promise.all(), Promise.race() | Requer combinação com Promise.all() | | Stack trace | Pode ser confuso em cadeias longas | Claro e direto | | Performance | Ligeiramente mais rápido (microssegundos) | Mesma base, overhead mínimo | | Suporte | ES6+ (2015) | ES8+ (2017) |

Veredito: qual usar?

Para quem busca código linear e fácil de ler, especialmente em fluxos sequenciais com múltiplos awaits, a escolha é async/await. É o padrão da indústria em 2025, recomendado pela MDN e pela maioria dos guias de estilo. Para quem precisa de concorrência real, com várias operações paralelas, ou está lidando com encadeamento complexo de Promises que exigem Promise.race() ou Promise.allSettled(), a escolha é Promises, ou, melhor ainda, uma combinação dos dois: async/await para o fluxo principal e Promise.all() para paralelismo.

O pódio do absurdo seria colocar um contra o outro como rivais. Na prática, async/await e Promises são aliados. Use async/await como sintaxe padrão e recorra a Promise.all() e amigos quando precisar de potência paralela. Seu código agradece, seu time agradece, e o debug não vira um episódio de terror.

Perguntas frequentes sobre async/await e Promises

Async/await substitui Promises?

Não. Async/await é açúcar sintático sobre Promises. Toda função async retorna uma Promise, e todo await espera uma Promise. Você continua usando Promises por baixo dos panos, a diferença é a forma de escrever.

Posso misturar async/await com .then()?

Sim, mas não é recomendado. Misturar os dois estilos confunde a leitura. Escolha um padrão e mantenha consistência no projeto. Se você está usando async/await, evite .then() dentro da mesma função.

Qual é mais rápido: async/await ou Promises?

Promises têm uma vantagem teórica de microssegundos por operação. Na prática, a diferença é desprezível. O ganho real de performance vem de escrever código correto, não da escolha entre as duas sintaxes.

Como tratar erros em async/await sem try/catch?

Você pode usar .catch() na Promise retornada pela função async: myAsyncFunction().catch(handleError). Mas dentro da função, o padrão é try/catch. Para erros não tratados, o Node.js dispara o evento 'unhandledRejection'.

Async/await funciona em todos os navegadores?

Async/await é suportado em todos os navegadores modernos (Chrome 55+, Firefox 52+, Safari 10.1+, Edge 15+). Para suporte a IE11 ou versões antigas, use Promises com polyfill ou um transpilador como Babel.

Quando devo usar Promise.all() com async/await?

Sempre que você tiver operações independentes que podem rodar em paralelo. Por exemplo, buscar dados de duas APIs diferentes ao mesmo tempo. Isso reduz o tempo total de espera drasticamente.

Zeca Maranhão

Editoria Novidades

Zeca Maranhão cobre o setor de meios de pagamento e crédito no Blog Sem Juízo. Análises técnicas, sem viés comercial.

Leia também · Novidades