Desenvolvimento Web

JavaScript assíncrono: Promises, async/await e como lidar com código que espera

JavaScript assíncrono: Promises, async/await e como lidar com código que espera

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.

Resposta rápida Orçamento sem compromisso +100 projetos entregues
Compartilhar: