O JavaScript opera em uma única thread, o que significa que ele executa uma instrução por vez. No entanto, para criar aplicações web modernas e fluidas, precisamos lidar com operações demoradas — como requisições de API ou leitura de arquivos — sem travar a interface do usuário. É aqui que entra a programação assíncrona. Entender a evolução dos Callbacks para o Async/Await é essencial para qualquer desenvolvedor que busca escrever código limpo e performático.
A Evolução: Da Confusão à Clareza
A Era dos Callbacks
Antigamente, usávamos funções de retorno (callbacks) para gerenciar o assincronismo. O problema surgia quando precisávamos de múltiplas operações dependentes, resultando no infame "Callback Hell" ou "Pirâmide do Destino".
// O código torna-se difícil de ler e manter rapidamente
buscarUsuario(id, (err, usuario) => {
if (err) return tratarErro(err);
buscarPedidos(usuario.id, (err, pedidos) => {
if (err) return tratarErro(err);
detalharPedido(pedidos[0].id, (err, detalhes) => {
if (err) return tratarErro(err);
console.log(detalhes); // Sucesso, mas a que custo?
});
});
});
Promises: Uma Promessa de Organização
As Promises introduziram uma forma mais elegante de encadear operações. Em vez de passar funções, passamos a lidar com objetos que representam o sucesso ou a falha de uma operação futura.
buscarUsuario(id) .then(usuario => buscarPedidos(usuario.id)) .then(pedidos => detalharPedido(pedidos[0].id)) .then(detalhes => console.log(detalhes)) .catch(err => tratarErro(err)) .finally(() => removerLoading());
Async/Await: O Açúcar Sintático Perfeito
Introduzido no ES2017, o async/await permite escrever código assíncrono que se parece com código síncrono, facilitando imensamente a leitura e o tratamento de exceções com blocos try/catch.
async function obterDadosCompletos(id) {
try {
const usuario = await buscarUsuario(id);
const pedidos = await buscarPedidos(usuario.id);
const detalhes = await detalharPedido(pedidos[0].id);
return detalhes;
} catch (err) {
tratarErro(err);
} finally {
removerLoading();
}
}
Criando e Gerenciando Suas Próprias Promises
Às vezes, você precisa transformar uma lógica baseada em tempo ou um evento antigo em uma Promise. Para isso, usamos o construtor new Promise.
function aguardar(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function carregarComLimite(url, tempoLimite) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error("Tempo esgotado")), tempoLimite);
fetch(url)
.then(res => {
clearTimeout(timer);
resolve(res);
})
.catch(err => {
clearTimeout(timer);
reject(err);
});
});
}
Execução em Paralelo vs Sequencial
Um erro comum é usar await em sequência quando as tarefas não dependem uma da outra, o que torna a aplicação lenta. Para máxima performance, devemos executar tarefas independentes simultaneamente.
// Executa tudo em paralelo - Falha se uma falhar
const [users, products] = await Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/products").then(r => r.json())
]);
// Promise.allSettled - Útil quando queremos os resultados mesmo com falhas parciais
const resultados = await Promise.allSettled([
fetch("/api/config"),
fetch("/api/analytics")
]);
resultados.forEach(res => {
if (res.status === "fulfilled") {
console.log("Sucesso:", res.value);
} else {
console.error("Falha em um dos itens:", res.reason);
}
});
Tratamento de Erros Moderno
Uma abordagem inspirada na linguagem Go é retornar uma tupla para evitar excesso de blocos try/catch aninhados.
// Função utilitária para capturar erros sem crashar
async function capturar(promise) {
try {
const data = await promise;
return [null, data];
} catch (err) {
return [err, null];
}
}
// Uso prático
const [erro, user] = await capturar(buscarUsuario(123));
if (erro) {
console.log("Erro ao buscar usuário:", erro.message);
} else {
console.log("Usuário encontrado:", user.nome);
}
Erros Comuns para Evitar
- Esquecer o
await: Se você não usar await, receberá o objeto Promise pendente em vez dos dados reais. - Loops Ineficientes: O
forEachnão funciona bem com async. Prefira ofor...ofouPromise.all()commap. - Promises não capturadas: Sempre termine suas correntes de Promise com
.catch()ou use try/catch para evitar erros globais (unhandled rejections). - Bloqueio desnecessário: Não use await em chamadas que podem rodar em segundo plano enquanto você processa outras informações.
Otimize seu Código JavaScript
Utilize nossas ferramentas gratuitas para formatar e comprimir seus scripts antes do deploy.