Boas Práticas de Tratamento de Erros Assíncronos em JavaScript na Prática para Times Ágeis Já leu

Entendendo Promises e seu Tratamento de Erros Uma Promise é a base do tratamento de erros assíncronos em JavaScript. Ela representa uma operação que pode estar pendente, resolvida ou rejeitada. Quando uma Promise é rejeitada, ela dispara um erro que precisa ser capturado. O método é a forma clássica de tratar esses erros, funcionando como um para operações assíncronos. Neste exemplo, o primeiro captura erros da requisição ou parsing JSON. Se houver um erro não tratado, o segundo será acionado. É importante entender que cada ou cria uma nova Promise, permitindo encadeamento seguro. Async/Await com Try-Catch O async/await é a sintaxe moderna e mais legível para trabalhar com Promises. Com ele, código assíncronos parece síncrono, e podemos usar tradicionais para tratar erros. Esta é a abordagem recomendada para novos projetos. https://api.example.com/users/${id} HTTP ${response.status}: ${response.statusText} Observe que o bloco executa sempre, independente de sucesso ou erro. Isso é útil para limpeza de recursos. Quando você relança um erro com , ele

Entendendo Promises e seu Tratamento de Erros

Uma Promise é a base do tratamento de erros assíncronos em JavaScript. Ela representa uma operação que pode estar pendente, resolvida ou rejeitada. Quando uma Promise é rejeitada, ela dispara um erro que precisa ser capturado. O método .catch() é a forma clássica de tratar esses erros, funcionando como um try-catch para operações assíncronos.

function buscarDados(url) {
  return fetch(url)
    .then(response => response.json())
    .catch(erro => {
      console.error('Erro ao buscar dados:', erro.message);
      return { dados: [] };
    });
}

buscarDados('https://api.example.com/users')
  .then(resultado => console.log(resultado))
  .catch(erro => console.error('Erro final:', erro));

Neste exemplo, o primeiro .catch() captura erros da requisição ou parsing JSON. Se houver um erro não tratado, o segundo .catch() será acionado. É importante entender que cada .catch() ou .then() cria uma nova Promise, permitindo encadeamento seguro.

Async/Await com Try-Catch

O async/await é a sintaxe moderna e mais legível para trabalhar com Promises. Com ele, código assíncronos parece síncrono, e podemos usar try-catch tradicionais para tratar erros. Esta é a abordagem recomendada para novos projetos.

async function buscarUsuario(id) {
  try {
    const response = await fetch(`https://api.example.com/users/${id}`);

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const usuario = await response.json();
    return usuario;
  } catch (erro) {
    console.error('Falha ao buscar usuário:', erro.message);
    throw erro;
  } finally {
    console.log('Requisição finalizada');
  }
}

// Uso
buscarUsuario(1)
  .then(user => console.log('Usuário:', user))
  .catch(erro => console.error('Não foi possível obter usuário'));

Observe que o bloco finally executa sempre, independente de sucesso ou erro. Isso é útil para limpeza de recursos. Quando você relança um erro com throw, ele propaga para a Promise retornada, permitindo tratamento em nível superior.

Tratando Múltiplas Operações Assíncronas

Quando você precisa executar várias operações em paralelo, Promise.all() é eficiente, mas rejeita se qualquer Promise falhar. Promise.allSettled() é mais seguro, pois aguarda todas as Promises e reporta o resultado de cada uma.

async function carregarDados() {
  try {
    const [usuarios, posts, comentarios] = await Promise.all([
      fetch('https://api.example.com/users').then(r => r.json()),
      fetch('https://api.example.com/posts').then(r => r.json()),
      fetch('https://api.example.com/comments').then(r => r.json())
    ]);

    return { usuarios, posts, comentarios };
  } catch (erro) {
    console.error('Uma ou mais requisições falharam:', erro);
    return null;
  }
}

// Versão com allSettled para mais controle
async function carregarDadosSeguro() {
  const resultados = await Promise.allSettled([
    fetch('https://api.example.com/users').then(r => r.json()),
    fetch('https://api.example.com/posts').then(r => r.json()),
    fetch('https://api.example.com/comments').then(r => r.json())
  ]);

  resultados.forEach((resultado, idx) => {
    if (resultado.status === 'rejected') {
      console.error(`Requisição ${idx} falhou:`, resultado.reason);
    }
  });

  return resultados;
}

Tratamento Avançado e Boas Práticas

Timeout e Retry com Exponential Backoff

Em sistemas reais, você precisa lidar com timeouts e implementar retry com backoff exponencial para recuperação automática de falhas temporárias.

async function fetchComTimeout(url, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(timeoutId);
    return await response.json();
  } catch (erro) {
    clearTimeout(timeoutId);
    if (erro.name === 'AbortError') {
      throw new Error('Requisição expirou');
    }
    throw erro;
  }
}

async function fetchComRetry(url, maxTentativas = 3, delayInicial = 1000) {
  let ultimoErro;

  for (let tentativa = 0; tentativa < maxTentativas; tentativa++) {
    try {
      return await fetchComTimeout(url);
    } catch (erro) {
      ultimoErro = erro;

      if (tentativa < maxTentativas - 1) {
        const delay = delayInicial * Math.pow(2, tentativa);
        console.log(`Tentativa ${tentativa + 1} falhou. Aguardando ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }

  throw ultimoErro;
}

// Uso
fetchComRetry('https://api.example.com/dados')
  .then(dados => console.log('Sucesso:', dados))
  .catch(erro => console.error('Todas as tentativas falharam:', erro.message));

Tratamento Centralizado de Erros

Em aplicações maiores, é bom centralizar a lógica de tratamento de erros usando middleware ou wrapper functions.

class GerenciadorErros {
  static async executar(funcao, contexto = '') {
    try {
      return await funcao();
    } catch (erro) {
      this.registrar(erro, contexto);
      this.notificarUsuario(erro);
      throw erro;
    }
  }

  static registrar(erro, contexto) {
    console.error(`[${contexto}] ${erro.message}`, erro.stack);
    // Enviar para serviço de logging
  }

  static notificarUsuario(erro) {
    // Mostrar mensagem amigável ao usuário
    const mensagem = erro.message.includes('timeout') 
      ? 'A operação demorou muito. Tente novamente.'
      : 'Ocorreu um erro inesperado. Tente mais tarde.';
    console.warn('Notificação:', mensagem);
  }
}

// Uso
GerenciadorErros.executar(
  () => fetchComRetry('https://api.example.com/dados'),
  'Carregamento de Dados'
)
  .then(dados => console.log(dados))
  .catch(() => {/* Já tratado */});

Conclusão

O tratamento de erros assíncronos em JavaScript é fundamental para aplicações robustas. Três pontos-chave para lembrar: Primeiro, prefira async/await com try-catch para código mais legível e manutenível. Segundo, sempre implemente timeout e retry com backoff exponencial em requisições de rede, pois falhas temporárias são comuns. Terceiro, centralize sua lógica de tratamento de erros para evitar duplicação e facilitar manutenção — seus colegas e seu eu do futuro agradecerão.

Referências


Artigos relacionados