Dominando Async e Await em JavaScript: Código Assíncrono com Aparência Síncrona em Projetos Reais Já leu

O Problema das Callbacks e Promises Antes de entender async/await, você precisa conhecer o contexto. JavaScript executa código de forma assíncrona naturalmente — operações como requisições HTTP, leitura de arquivos e timers não bloqueiam a execução. Historicamente, usávamos callbacks para lidar com essas operações, mas isso levava ao famoso "callback hell": código aninhado, difícil de ler e manter. As Promises vieram como solução, permitindo uma sintaxe mais limpa com e , mas ainda não era intuitivo como código síncrono. Entendendo Async/Await Async/await é açúcar sintático sobre Promises que permite escrever código assíncrono com aparência síncrona. Uma função sempre retorna uma Promise, e a palavra-chave pausa a execução até que uma Promise seja resolvida. Isso torna o fluxo de controle muito mais legível e intuitivo para quem está acostumado com programação síncrona tradicional. A função , e retornam Promises. O pausa a execução até que cada uma seja resolvida, e o resultado é atribuído diretamente à variável. Se alguma Promise for

O Problema das Callbacks e Promises

Antes de entender async/await, você precisa conhecer o contexto. JavaScript executa código de forma assíncrona naturalmente — operações como requisições HTTP, leitura de arquivos e timers não bloqueiam a execução. Historicamente, usávamos callbacks para lidar com essas operações, mas isso levava ao famoso "callback hell": código aninhado, difícil de ler e manter. As Promises vieram como solução, permitindo uma sintaxe mais limpa com .then() e .catch(), mas ainda não era intuitivo como código síncrono.

// Callback Hell (difícil de ler)
fetchUser(userId, function(err, user) {
  if (err) console.error(err);
  fetchPosts(user.id, function(err, posts) {
    if (err) console.error(err);
    fetchComments(posts[0].id, function(err, comments) {
      console.log(comments);
    });
  });
});

// Com Promises (melhor, mas ainda verboso)
fetchUser(userId)
  .then(user => fetchPosts(user.id))
  .then(posts => fetchComments(posts[0].id))
  .then(comments => console.log(comments))
  .catch(err => console.error(err));

Entendendo Async/Await

Async/await é açúcar sintático sobre Promises que permite escrever código assíncrono com aparência síncrona. Uma função async sempre retorna uma Promise, e a palavra-chave await pausa a execução até que uma Promise seja resolvida. Isso torna o fluxo de controle muito mais legível e intuitivo para quem está acostumado com programação síncrona tradicional.

// Agora a mesma lógica é clara e linear
async function processUserData(userId) {
  try {
    const user = await fetchUser(userId);
    const posts = await fetchPosts(user.id);
    const comments = await fetchComments(posts[0].id);
    console.log(comments);
  } catch (err) {
    console.error(err);
  }
}

// Chamar uma função async
processUserData(1);

A função fetchUser, fetchPosts e fetchComments retornam Promises. O await pausa a execução até que cada uma seja resolvida, e o resultado é atribuído diretamente à variável. Se alguma Promise for rejeitada, o bloco catch intercepta o erro. Isso é muito mais legível do que encadear múltiplos .then().

Padrões Avançados e Armadilhas Comuns

Paralelismo vs. Sequência

Um erro comum é usar await desnecessariamente em sequência quando operações podem rodar em paralelo. Se você tem duas requisições independentes, executá-las em série é ineficiente. Use Promise.all() para operações que não dependem uma da outra.

// ❌ Lento: espera 2 segundos (1s + 1s)
async function lento() {
  const user = await fetchUser(1);      // 1 segundo
  const settings = await fetchSettings(1); // 1 segundo
  return { user, settings };
}

// ✅ Rápido: espera ~1 segundo
async function rapido() {
  const [user, settings] = await Promise.all([
    fetchUser(1),
    fetchSettings(1)
  ]);
  return { user, settings };
}

Tratamento de Erros

O try/catch é o padrão recomendado, mas você pode também encadear .catch() em uma Promise. Para erros em múltiplas operações paralelas, Promise.all() rejeita assim que uma falha, enquanto Promise.allSettled() espera todas terminarem (sucesso ou falha).

// Promise.allSettled aguarda todas as operações
async function tratarMultiplosErros() {
  const resultados = await Promise.allSettled([
    fetchUser(1),
    fetchUser(2),
    fetchUser(999) // vai falhar
  ]);

  resultados.forEach((resultado, index) => {
    if (resultado.status === 'fulfilled') {
      console.log(`User ${index}:`, resultado.value);
    } else {
      console.log(`User ${index} falhou:`, resultado.reason);
    }
  });
}

Loops Assíncrono

Quando você precisa iterar sobre um array e aguardar operações dentro do loop, cuidado: forEach() não aguarda promises. Use for...of ou .reduce() para controlar a sequência.

// ❌ Incorreto: não aguarda as promises
[1, 2, 3].forEach(async (id) => {
  await fetchUser(id); // executa em paralelo, não em sequência
});

// ✅ Correto: aguarda sequencialmente
async function buscarSequencialmente() {
  for (const id of [1, 2, 3]) {
    const user = await fetchUser(id);
    console.log(user);
  }
}

// ✅ Alternativa: em paralelo com Promise.all()
async function buscarEmParalelo() {
  const usuarios = await Promise.all(
    [1, 2, 3].map(id => fetchUser(id))
  );
  console.log(usuarios);
}

Funções Auxiliares: Implementação Real

// Simular requisições HTTP (para teste)
function fetchUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) {
        resolve({ id, name: `User ${id}` });
      } else {
        reject(new Error('ID inválido'));
      }
    }, 1000);
  });
}

// Usar em uma aplicação real
async function main() {
  try {
    const user = await fetchUser(1);
    console.log('Usuário encontrado:', user);

    const multiplos = await Promise.all([
      fetchUser(1),
      fetchUser(2),
      fetchUser(3)
    ]);
    console.log('Múltiplos usuários:', multiplos);
  } catch (err) {
    console.error('Erro:', err.message);
  }
}

main();

Conclusão

Async/await revoluciona a forma como lidamos com código assíncrono em JavaScript, tornando-o tão legível quanto síncrono. Primeiro aprendizado: sempre prefira async/await sobre callbacks e evite o excesso de .then() — o código fica mais limpo e fácil de manter. Segundo aprendizado: lembre-se que await pausa a execução, então use Promise.all() quando operações são independentes para não desperdiçar performance. Terceiro aprendizado: dominando loops assíncrono e tratamento de erros com try/catch, você terá total controle sobre fluxos complexos de dados e requisições.

Referências


Artigos relacionados