Buscar dados de uma API, ler um arquivo ou esperar a resposta de um servidor leva tempo. Se o JavaScript parasse tudo para aguardar cada uma dessas tarefas, a página inteira travaria. A solução é o código assíncrono, e as Promises junto com o async/await são as ferramentas modernas para escrevê-lo de forma legível.
O problema: tarefas que demoram
O JavaScript executa uma instrução por vez. Quando você pede dados a um servidor, a resposta não chega na hora. Em vez de bloquear a execução, a linguagem registra o pedido, segue com o restante do código e volta ao resultado quando ele estiver pronto.
Antigamente, isso era resolvido com callbacks: funções passadas como argumento para serem chamadas no fim da tarefa. O problema aparecia quando uma tarefa dependia da outra, e os callbacks iam se aninhando em camadas difíceis de ler, o famoso “callback hell”.
O que é uma Promise
Uma Promise (promessa) é um objeto que representa o resultado futuro de uma operação assíncrona. Pense em um pedido de comida: você recebe um comprovante agora e, mais tarde, o pedido chega ou dá algum problema. Uma Promise tem sempre um destes três estados:
- Pendente (pending): a operação ainda está em andamento.
- Resolvida (fulfilled): terminou com sucesso e entregou um valor.
- Rejeitada (rejected): falhou e entregou um motivo de erro.
Depois de resolvida ou rejeitada, a Promise não muda mais de estado. Para reagir ao resultado, usamos os métodos .then() (sucesso), .catch() (erro) e .finally() (executa em ambos os casos).
fetch("https://exemplo.com/api/usuarios")
.then(resposta => resposta.json())
.then(dados => console.log(dados))
.catch(erro => console.error("Falhou:", erro))
.finally(() => console.log("Fim da requisição"));
Cada .then() devolve uma nova Promise, o que permite encadear etapas em sequência sem aninhar funções. Já é bem melhor do que callbacks, mas ainda dá para deixar mais limpo.
async/await: Promises com cara de código comum
O async/await é uma sintaxe que funciona por cima das Promises. Duas regras bastam para começar:
- Uma função marcada com
asyncsempre devolve uma Promise. - Dentro dela, o
awaitpausa a execução daquela função até a Promise ser resolvida e devolve o valor final.
async function carregarUsuarios() {
try {
const resposta = await fetch("https://exemplo.com/api/usuarios");
const dados = await resposta.json();
console.log(dados);
} catch (erro) {
console.error("Falhou:", erro);
} finally {
console.log("Fim da requisição");
}
}
O resultado faz exatamente o mesmo que o exemplo anterior, mas lê-se de cima para baixo, como um código síncrono. O tratamento de erros também fica familiar: o bloco try/catch captura tanto falhas de rede quanto erros lançados dentro da função.
Um detalhe importante: o await pausa apenas a função em que está. O restante da aplicação continua funcionando normalmente, e a página não congela.
Executando tarefas em paralelo com Promise.all
Um erro comum é usar await em sequência para tarefas que não dependem uma da outra. Isso faz uma esperar pela outra sem necessidade:
// Sequencial: a segunda só começa quando a primeira termina
const usuarios = await buscarUsuarios();
const produtos = await buscarProdutos();
// Paralelo: as duas começam juntas
const [usuarios2, produtos2] = await Promise.all([
buscarUsuarios(),
buscarProdutos()
]);
O Promise.all recebe uma lista de Promises e devolve uma nova Promise que resolve quando todas terminam. Se uma delas for rejeitada, o conjunto inteiro é rejeitado.
Comparando as três abordagens
| Abordagem | Como funciona | Principal vantagem | Principal limitação |
|---|---|---|---|
| Callbacks | Função chamada ao fim da tarefa | Simples para casos pequenos | Aninhamento difícil de ler |
| Promises (.then) | Encadeamento de etapas | Fluxo em cadeia e erro centralizado | Cadeias longas ainda cansam |
| async/await | Espera com sintaxe sequencial | Leitura natural e try/catch | Exige atenção para não serializar tarefas |
Erros comuns e como evitá-los
- Esquecer o await: sem ele, você recebe a Promise em si, e não o valor. Se aparecer
Promise { <pending> }no console, é um bom sinal de que o await ficou de fora. - Não tratar erros: uma Promise rejeitada sem
catchgera um erro não tratado. Use sempretry/catchou.catch(). - Esquecer de verificar a resposta do fetch: o
fetchsó rejeita em falhas de rede. Respostas como 404 ou 500 não são rejeitadas automaticamente, então vale checarresposta.ok. - Usar await dentro de loops sem necessidade: quando as tarefas são independentes, prefira
Promise.allpara ganhar tempo. - Usar await fora de função async: em scripts comuns, isso causa erro de sintaxe. Em módulos modernos, o await no nível superior é permitido.
Quando usar cada ferramenta
Na prática, o async/await é a escolha mais comum no dia a dia, porque deixa o fluxo claro. As Promises com .then() continuam úteis em trechos curtos e em bibliotecas que devolvem Promises. E é útil saber ler os dois estilos, já que você os encontrará misturados em projetos reais.
Um exemplo prático: carregar dados e mostrar na tela
Imagine uma página que precisa exibir uma lista de tarefas vinda de um servidor. O fluxo costuma ter quatro passos: mostrar uma mensagem de carregamento, pedir os dados, tratar uma eventual falha e, por fim, desenhar o resultado. Com async/await, cada passo ocupa uma linha ou duas e a ordem fica evidente para quem lê o código.
async function mostrarTarefas() {
const lista = document.querySelector("#tarefas");
lista.textContent = "Carregando...";
try {
const resposta = await fetch("/api/tarefas");
if (!resposta.ok) throw new Error("Erro " + resposta.status);
const tarefas = await resposta.json();
lista.innerHTML = tarefas.map(t => "<li>" + t.titulo + "</li>").join("");
} catch (erro) {
lista.textContent = "Não foi possível carregar as tarefas.";
}
}
Repare que a verificação de resposta.ok transforma um erro HTTP em uma exceção, que cai direto no catch. Assim, o usuário sempre vê uma mensagem clara, seja qual for o motivo da falha. Esse padrão de carregar, validar e exibir aparece em quase toda aplicação web que consome uma API, e dominá-lo já resolve boa parte dos casos reais.
Conclusão
Código assíncrono deixa de ser um mistério quando você entende que uma Promise é só um objeto que representa um resultado futuro, e que o async/await é uma forma mais agradável de esperar por ele. Com try/catch para erros e Promise.all para paralelismo, você já resolve a maioria dos casos do dia a dia.
Se você quer praticar esses conceitos com projetos reais, vale conhecer os cursos de programação front-end e de JavaScript da Cursa, que levam você do básico ao consumo de APIs passo a passo.



























