Dominar Async/Await: Gestão Avançada de Erros e Padrões de Retry
Async/await transformou o JavaScript assíncrono, mas a gestão correta de erros continua a ser um desafio. Este artigo analisa uma implementação robusta que inclui lógica de retry, gestão de timeouts, estratégias de fallback e padrões de recuperação de erros essenciais em aplicações de produção.

Introdução: Para Além do Async/Await Básico
Embora a sintaxe async/await torne o código assíncrono mais legível, aplicações de produção exigem uma gestão sofisticada de erros, mecanismos de retry, timeouts e estratégias de fallback. Falhas de rede, limites de API e erros transitórios exigem código resiliente que lide elegantemente com falhas. Vamos analisar uma implementação completa de async/await que aborda desafios do mundo real.
Código Original: Armadilhas Comuns
A implementação inicial tinha vários problemas críticos que podiam causar falhas em produção. Vamos examinar a versão corrigida e perceber o que estava errado:
Código Corrigido: Cliente API Resiliente
class APIClient { ... } // Código completo omitido para brevidadeErros Críticos Corrigidos
O código original tinha vários erros graves. Primeiro, o AbortController era criado dentro do bloco try, mas precisava de estar acessível no catch para a limpeza correta. Segundo, o método isRetryableError podia receber parâmetros null/undefined, causando erros de execução — corrigido com verificações de null. Terceiro, AbortError não era distinguido de outros erros, podendo causar retries infinitos em cancelamentos manuais. Quarto, o objeto error não usava optional chaining ao aceder a error.message em handleFailure, arriscando falhas quando error era undefined.
Implementação de Timeout com Promise.race
O código usa Promise.race para implementar de forma elegante a funcionalidade de timeout. Isto faz competir a requisição fetch real contra uma promise de timeout. Quem resolver ou rejeitar primeiro ganha. Quando o timeout dispara, controller.abort() é chamado para cancelar a requisição fetch, evitando desperdício de largura de banda e memória. Este padrão é superior a simplesmente envolver fetch num timeout, porque cancela ativamente a requisição em vez de apenas a ignorar.
Backoff Exponencial com Jitter
O método calculateBackoff implementa backoff exponencial — cada retry espera progressivamente mais tempo (1s, 2s, 4s, 8s). Isto evita sobrecarregar um servidor com dificuldades. O jitter (atraso aleatório) evita o problema do 'thundering herd', onde múltiplos clientes tentam retry ao mesmo tempo, podendo causar falhas em cascata. Esta combinação é padrão na indústria para lógica de retries em sistemas distribuídos, usada por AWS, Google Cloud e APIs principais.
Lógica Inteligente de Retry com Verificações de Segurança
O método isRetryableError melhorado inclui agora verificações de segurança essenciais. Primeiro valida que pelo menos um parâmetro foi fornecido, evitando erros de referência a null. Verifica explicitamente AbortError e retorna false — cancelamentos manuais nunca devem ser retried. O método distingue erros transitórios que merecem retry (falhas de rede, timeouts, erros 5xx, limites 429) de falhas permanentes (400, 404, 401). Esta tomada de decisão inteligente é crucial em sistemas de produção.
Contexto de Erro Aprimorado
O código corrigido tenta analisar o corpo das respostas de erro, fornecendo mais contexto quando ocorre uma falha. Anexa o objeto response e o body analisado aos erros lançados, permitindo ao código que chamou aceder a informações detalhadas. O uso de optional chaining (?.) evita falhas ao aceder a propriedades potencialmente undefined. Esta abordagem defensiva garante que o cliente permanece estável mesmo com respostas inesperadas da API.
Gestão Estruturada de Erros
Em vez de simplesmente lançar erros, esta implementação devolve objetos de resposta estruturados com flags de sucesso, dados, mensagens de erro e metadados como contagem de tentativas. Este método fornece ao código chamador informação completa para decisões informadas — exibir mensagens amigáveis, registar erros ou acionar workflows alternativos. O formato consistente simplifica a gestão de erros no resto da aplicação.
Estratégias de Fallback com Segurança
Quando todos os retries falham, handleFailure implementa uma estratégia de degradação graciosa com gestão adequada de null. O optional chaining garante que o código não falha quando error é undefined. Se existirem dados de fallback (por exemplo de uma requisição anterior ou valores por defeito), estes são retornados em vez de falhar completamente. Isto permite que a aplicação continue funcional mesmo durante falhas totais da API. A flag fromCache permite ao UI indicar quando os dados podem estar desatualizados.
Padrão de Loop For para Retries
Usar um loop for com continue para a lógica de retry é mais limpo do que abordagens recursivas ou while. Tornando explícito o número máximo de retries, previne stack overflow e clarifica o fluxo. O contador de tentativas acompanha o progresso e ajuda no logging. Sair do loop ao encontrar erros não-retryable evita atrasos desnecessários. Este padrão é mais legível e fácil de manter do que alternativas.
Gestão do Escopo do AbortController
O código corrigido declara AbortController ao nível do loop, garantindo acesso em cada iteração. Isto permite cancelamento correto em cenários de timeout e erro. Criar um novo AbortController para cada tentativa permite controlo preciso sobre o cancelamento da requisição. O fetch moderno com AbortController é o padrão para operações assíncronas canceláveis em JavaScript, substituindo padrões antigos como tokens de cancelamento de promise.
Cobertura Completa de Métodos HTTP
A implementação corrigida adiciona métodos PUT e DELETE juntamente com GET e POST, proporcionando suporte completo a operações CRUD. Cada método configura corretamente o tipo de request e, quando apropriado, serializa o body em JSON. Isto torna o cliente mais versátil para interações reais com APIs, onde todos os métodos HTTP são frequentemente necessários.
Exemplo de Uso Aprimorado
A função de demonstração inclui agora saída de consola mais informativa com indicadores visuais (✓, ⚠, ℹ, ✗) que facilitam a leitura dos logs. O tratamento da flag fromCache informa corretamente os utilizadores quando os dados podem estar desatualizados. A gestão de erros distingue entre respostas degradadas e erros inesperados, fornecendo feedback adequado em cada cenário.
Considerações de Produção
Implementações de produção devem adicionar padrões de circuit breaker para prevenir falhas em cascata, limitação de taxa para respeitar quotas de API, deduplicação de requests para evitar chamadas redundantes e tipagem de erros abrangente para diferentes modos de falha. A integração com ferramentas de monitorização ajuda a rastrear taxas de retry, padrões de falha e métricas de desempenho. A lógica de refresh de tokens de autenticação normalmente pertence a esta camada. Considere usar bibliotecas estabelecidas como axios com interceptors ou ky para implementações testadas em produção.
Principais Lições
- Sempre valide parâmetros nas funções de verificação de erros para evitar null reference errors.
- Use optional chaining (?.) ao aceder a propriedades potencialmente undefined para evitar falhas.
- Declare AbortController no escopo apropriado para garantir acesso à limpeza.
- Distinga AbortError de outros erros — cancelamentos manuais não devem ser retried.
- Anexe contexto detalhado do erro (response, body) aos erros lançados para melhor debug.
- Promise.race implementa elegantemente timeouts competindo fetch com uma promise de timeout.
- Backoff exponencial com jitter previne sobrecarga de servidores e o problema do thundering herd.
- Objetos de resposta estruturados com flags de sucesso fornecem resultados consistentes e informativos.
- Estratégias de fallback permitem degradação graciosa durante falhas completas da API.
- Loops for fornecem lógica de retry clara e segura para a stack, superior à recursão.
- Cobertura completa de métodos HTTP (GET, POST, PUT, DELETE) torna o cliente mais versátil.
- Programação defensiva com verificações de null e optional chaining evita falhas inesperadas.