Executando verificação de segurança...
3

Descomplicando Promises e Async/Await: O Guia para Tratar Erros em JavaScript

No desenvolvimento JavaScript moderno, lidar com operações assíncronas de forma eficiente é essencial.

Seja chamando APIs, lendo arquivos ou aguardando um timeout, entender Promises, async/await e como gerenciar erros adequadamente pode ajudar você a escrever código mais legível, manutenível e resistente a bugs.

Este artigo percorre os fundamentos e exemplos práticos de Promises em JavaScript, async/await e estratégias para tratamento de erros.

O que são Promises em JavaScript?

Uma Promise em JavaScript representa a eventual conclusão (ou falha) de uma operação assíncrona e seu valor resultante.

Sintaxe Básica de uma Promise

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { name: 'Vinícius' };
      resolve(data);
    }, 1000);
  });
};

Uma Promise pode estar em um de três estados:

  • Pending – A operação está em andamento.
  • Fulfilled – A operação foi concluída com sucesso.
  • Rejected – A operação falhou.

Consumindo Promises

fetchData()
  .then(response => {
    console.log(response);
  })
  .catch(error => {
    console.error(error);
  });

Aqui, .then() trata a resolução bem-sucedida, e .catch() trata os erros. Mas aninhar muitos desses pode rapidamente levar a uma pirâmide no estilo callback, também conhecida como "Promise hell."

Introduzindo async e await

ES2017 introduziu a sintaxe async e await para lidar com Promises de uma forma mais limpa e legível.

Como Funciona

const getUser = async () => {
  try {
    const response = await fetchData();
    console.log(response);
  } catch (error) {
    console.error('Error fetching user:', error);
  }
};
  • async declara que a função sempre retorna uma Promise.
  • await pausa a execução até que a Promise seja resolvida ou rejeitada.

Isso torna o código mais limpo e fácil de debugar.

"Código assíncrono não precisa ser difícil de escrever ou entender. async/await é prova disso." – Kyle Simpson, autor de You Don't Know JS

Exemplo Real: Buscando Dados de API

Vamos usar uma API real para demonstrar async/await e tratamento de erros:

const getUserData = async (userId) => {
  try {
    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
    if (!res.ok) {
      throw new Error('Network response was not ok');
    }
    const user = await res.json();
    return user;
  } catch (error) {
    console.error('Failed to fetch user:', error);
    throw error;
  }
};

Chamando a função:

getUserData(1)
  .then(user => console.log(user))
  .catch(err => console.log('Handled in calling function:', err));

Melhores Práticas para Tratamento de Erros

1. Sempre Use try/catch com await

Sem isso, Promises rejeitadas irão quebrar sua função async silenciosamente ou propagar erros não capturados.

2. Use .catch() Quando Você Deve Permanecer na Sintaxe de Encadeamento

doSomething()
  .then(result => doNext(result))
  .catch(error => console.error('Something went wrong:', error));

3. Envolva Chamadas Assíncronas em Funções Utilitárias

Este padrão evita repetir blocos try/catch:

const safe = async (promise) => {
  try {
    const data = await promise;
    return [null, data];
  } catch (err) {
    return [err, null];
  }
};

const [err, data] = await safe(fetchData());
if (err) return console.error('Error:', err);

Armadilhas para Evitar

Falhas Silenciosas

Esquecer de return uma Promise ou não usar await pode levar a comportamentos inesperados.

// Errado
const result = getUserData(1); // retorna Promise, não dados reais

// Correto
const result = await getUserData(1);

Ignorando Rejeições

Rejeições não tratadas podem quebrar sua aplicação no Node.js (a partir da v15+ por padrão).

Misturando .then() e await

Funciona, mas é confuso e inconsistente. Mantenha um estilo por função.

Quando Usar Promises vs async/await

Caso de UsoAbordagem Preferida
Operações únicasasync/await
Encadeando múltiplas operaçõesasync/await
Cadeias construídas dinamicamente.then()
Fluxos reativos ou stream-likePromises / RxJS

Bônus: Lidando com Múltiplas Promises

Usando Promise.all()

const fetchPostsAndUsers = async () => {
  try {
    const [posts, users] = await Promise.all([
      fetch('https://jsonplaceholder.typicode.com/posts'),
      fetch('https://jsonplaceholder.typicode.com/users')
    ]);

    const postsData = await posts.json();
    const usersData = await users.json();
    console.log(postsData, usersData);
  } catch (error) {
    console.error('Error fetching posts or users:', error);
  }
};

Isso é ótimo quando você quer executar múltiplas tarefas assíncronas em paralelo e aguardar que todas terminem.

Conclusão

Entender como Promises, async/await e tratamento de erros funcionam em JavaScript é fundamental para construir aplicações rápidas, responsivas e estáveis. Essas ferramentas são essenciais ao interagir com APIs, lidar com I/O ou construir recursos em tempo real.

Para recapitular:

  • Use async/await para código mais limpo e com aparência síncrona.
  • Sempre trate erros com try/catch ou .catch().
  • Envolva lógica assíncrona em padrões reutilizáveis quando possível.

Nas palavras de Douglas Crockford, uma das principais vozes do JavaScript:

"JavaScript é a linguagem de programação mais mal interpretada do mundo." — e é seu trabalho entendê-la melhor a cada dia.

Referências

  1. FLANAGAN, David. JavaScript: The Definitive Guide. 7th ed. O'Reilly Media, 2020.
  2. SIMPSON, Kyle. You Don't Know JS Yet. 2nd ed. GitHub Open Source Series, 2020.
  3. Mozilla Developer Network. MDN Web Docs: Promise
  4. W3Schools. JavaScript async/await
Carregando publicação patrocinada...