JavaScript é single-threaded — executa uma coisa de cada vez. Isso levanta a questão: como o browser consegue buscar dados de uma API, animações, timers e eventos de usuário ao mesmo tempo? A resposta é o modelo assíncrono: operações lentas (requisições de rede, leitura de arquivo, timers) são iniciadas e o JavaScript continua executando outro código enquanto espera. Quando a operação termina, um callback é chamado. Entender esse modelo é fundamental para JavaScript moderno — e é onde muitos desenvolvedores inicialmente travam.
O problema dos callbacks aninhados
Antes das Promises, operações assíncronas usavam callbacks — funções passadas como argumento para serem chamadas quando a operação terminasse. Para operações dependentes em sequência, os callbacks se aninhavam:
// Callback hell — difícil de ler e de tratar erros
buscarUsuario(id, function(usuario) {
buscarPedidos(usuario.id, function(pedidos) {
buscarDetalhesPedido(pedidos[0].id, function(detalhes) {
// 4 níveis de aninhamento e contando...
console.log(detalhes)
}, function(erro) { /* tratamento de erro */ })
}, function(erro) { /* tratamento de erro */ })
}, function(erro) { /* tratamento de erro */ })
Promises: uma forma melhor de lidar com o futuro
Uma Promise representa um valor que pode estar disponível agora, no futuro, ou nunca. Tem três estados: pending (pendente), fulfilled (resolvida com sucesso), rejected (rejeitada com erro):
// Criando uma Promise
function esperar(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(`Esperou ${ms}ms`), ms)
})
}
// Consumindo com .then() e .catch()
esperar(1000)
.then(resultado => console.log(resultado)) // "Esperou 1000ms"
.catch(erro => console.error(erro))
// Promises em cadeia — muito mais legível que callbacks
fetch('/api/usuario/42')
.then(response => response.json())
.then(usuario => fetch(`/api/pedidos?userId=${usuario.id}`))
.then(response => response.json())
.then(pedidos => console.log(pedidos))
.catch(erro => console.error("Erro:", erro))
async/await: Promises com sintaxe síncrona
async/await é “açúcar sintático” sobre Promises — por baixo dos panos continua sendo Promise, mas a sintaxe parece código síncrono e é muito mais fácil de ler:
async function carregarDadosUsuario(userId) {
try {
const response = await fetch(`/api/usuarios/${userId}`)
if (!response.ok) {
throw new Error(`Erro HTTP: ${response.status}`)
}
const usuario = await response.json()
const pedidosResponse = await fetch(`/api/pedidos?userId=${usuario.id}`)
const pedidos = await pedidosResponse.json()
return { usuario, pedidos }
} catch (erro) {
console.error("Falha ao carregar dados:", erro)
throw erro // Re-lança para o chamador tratar
}
}
// Calling an async function
carregarDadosUsuario(42)
.then(dados => console.log(dados))
.catch(erro => console.error(erro))
// Ou no contexto de outra função async:
async function main() {
const dados = await carregarDadosUsuario(42)
console.log(dados)
}
Executando Promises em paralelo
Quando múltiplas operações são independentes, execute-as em paralelo com Promise.all:
async function carregarDashboard(userId) {
// Sequencial: uma espera a outra — desnecessário e lento
// const usuario = await buscarUsuario(userId)
// const pedidos = await buscarPedidos(userId)
// const notificacoes = await buscarNotificacoes(userId)
// Paralelo: todas iniciam simultaneamente — mais rápido
const [usuario, pedidos, notificacoes] = await Promise.all([
buscarUsuario(userId),
buscarPedidos(userId),
buscarNotificacoes(userId),
])
return { usuario, pedidos, notificacoes }
}
// Promise.allSettled: aguarda todas, mesmo que algumas falhem
const resultados = await Promise.allSettled([
fetch('/api/servico-a'),
fetch('/api/servico-b'), // pode falhar sem cancelar as outras
fetch('/api/servico-c'),
])
resultados.forEach(resultado => {
if (resultado.status === 'fulfilled') console.log(resultado.value)
else console.error('Falhou:', resultado.reason)
})
O modelo assíncrono do JavaScript é mais fácil de dominar do que parece inicialmente. async/await tornou o código assíncrono praticamente tão legível quanto síncrono — a maioria dos erros com código assíncrono hoje vem de esquecer o await ou não tratar erros com try/catch. Pratique consumindo APIs públicas com fetch() e async/await — em algumas horas, o padrão se torna completamente natural.
Tem um projeto em mente?
Somos especialistas em transformar ideias em produtos digitais. Apps, sites, automações e IA — vamos construir juntos.