Erros Personalizados em JavaScript
JavaScript permite criar tipos de erro personalizados estendendo a classe Error nativa, possibilitando um tratamento de erros mais detalhado.
Erros Personalizados em JavaScript
O JavaScript permite criar seus próprios tipos de erro estendendo a classe Error nativa. Um erro personalizado é simplesmente uma classe que herda de Error (ou de outro tipo de erro) e carrega um name significativo, além de quaisquer dados extras que a situação exigir.
Esta página aborda por que você desejaria usar erros personalizados, como defini-los corretamente, como distinguir tipos de erro com instanceof, como adicionar contexto (códigos de status, nomes de campos, o cause original) e como organizar uma hierarquia de erros para uma aplicação real.
Por que criar erros personalizados?
Lançar um simples new Error("...") funciona, mas oferece ao chamador apenas uma string para inspecionar. Os erros personalizados resolvem três problemas:
- Tratamento baseado em tipo. Com
instanceof ValidationErrorvocê pode reagir a um tipo específico de falha sem precisar analisar o texto da mensagem, o que é frágil e dependente de localização. - Contexto adicional. Uma classe personalizada pode carregar campos estruturados — um
statusCodeHTTP, ofieldproblemático, uma dica de nova tentativa — em vez de comprimir tudo na mensagem. - Hierarquias claras. Uma classe base comum (por exemplo,
AppError) permite que um handler de nível superior capture todos os erros da sua aplicação com um únicoinstanceof, ao mesmo tempo que permite que o código interno lance subtipos específicos.
Se você precisa aprender primeiro a mecânica de lançamento/captura, leia Tratamento de erros: try...catch. Os erros personalizados se baseiam diretamente em herança de classes e extensão de classes nativas.
Fundamentos da Extensão da Classe Error
Para criar um erro personalizado, você extend a classe Error. A nova classe herda message, stack e toString() de Error, e você adiciona o que mais precisar.
Criando uma Classe de Erro Personalizada
Este é o padrão mínimo:
class ValidationError extends Error {
constructor(message) {
super(message); // Pass the message to the Error constructor (sets this.message)
this.name = "ValidationError"; // Override the default name "Error"
}
}Dois detalhes são importantes:
super(message)deve vir primeiro. Dentro do construtor de uma subclasse, você não pode usarthisantes de chamarsuper(). O construtor deErrordefinethis.messagee captura o stack trace.- Defina
this.name. Sem isso, o erro aparece como"Error"em mensagens e emtoString(). Definirnametorna os logs e o padrãoswitch (error.name)mais legíveis.
Você também pode encontrar Object.setPrototypeOf(this, new.target.prototype) em alguns exemplos. Com transpiladores modernos e classes nativas, isso geralmente é desnecessário, mas não causa problemas e garante que instanceof funcione mesmo quando o código é compilado para ES5 ou compartilhado entre realms (iframes/fronteiras de worker). Os exemplos abaixo mantêm isso por segurança.
Uma vez criado, o erro se comporta como qualquer outro:
const e = new ValidationError("bad input");
e.name; // "ValidationError"
e instanceof Error; // true — it is still a real Error
e.toString(); // "ValidationError: bad input"Usando Erros Personalizados
Depois de definir um erro personalizado, você pode lançá-lo na sua aplicação como qualquer erro padrão:
Neste exemplo, um e-mail é validado contra uma expressão regular. Se a validação falhar, um ValidationError é lançado. Esse erro é então capturado no bloco try...catch e, se for uma instância de ValidationError, uma mensagem específica é registrada.
Tratando Múltiplos Tipos de Erros Personalizados
Você pode querer criar vários tipos de erros para diferentes partes da sua aplicação. Veja como tratar múltiplos erros personalizados:
Essa estrutura permite o tratamento distinto de diferentes tipos de erro, tornando a aplicação mais robusta e fácil de depurar.
Prefira instanceof em vez de comparar strings error.name quando possível: instanceof também corresponde a subclasses, portanto sobrevive melhor a refatorações.
Construindo uma Hierarquia de Erros com uma Classe Base
Em aplicações reais, vale a pena dar a todos os seus erros um ancestral comum. Um handler de nível superior pode então capturar todos os erros "esperados" da aplicação em um único lugar, ao mesmo tempo que permite que o código mais interno lance subtipos precisos. A opção cause (ES2022) permite que um erro de nível superior envolva o erro de nível inferior que o desencadeou, preservando o original para depuração.
Aqui, this.name = this.constructor.name elimina a necessidade de repetir o nome em cada subclasse, e a única verificação instanceof AppError captura NotFoundError, ConflictError e qualquer subtipo futuro.
Tratamento Avançado de Erros Personalizados em JavaScript
Expandindo os conceitos básicos, podemos aplicar erros personalizados a cenários mais complexos, como operações assíncronas e casos específicos de lógica de negócio.
Exemplo 1: Tratamento Personalizado de Erros de API
Este exemplo demonstra como criar e usar um erro personalizado para lidar com problemas em requisições de API, como quando um recurso solicitado não é encontrado ou o servidor retorna um erro. O fluxo async/await aqui se combina com o tratamento de erros com promises.
Explicação
- Classe ApiError: Esta classe personalizada captura erros específicos de API, armazenando o código de status HTTP junto com uma mensagem personalizada.
- Função fetchData: Tenta buscar dados de uma URL fornecida. Se a resposta não for bem-sucedida, lança um
ApiErrorcom uma mensagem detalhada e o código de status. - Tratamento de Erros: Os erros são capturados e tratados adequadamente. Erros relacionados à API são registrados com informações detalhadas, enquanto erros inesperados também são capturados e registrados.
Exemplo 2: Tratamento Personalizado de Erros de Validação
Use este exemplo para tratar erros relacionados à validação de dados, como a verificação de entrada do usuário.
Explicação
- Classe ValidationError: Uma classe de erro personalizada que ajuda a identificar qual campo específico dos dados de entrada falhou na verificação de validação.
- Função validateUser: Verifica a validade dos dados do usuário. Se os dados não atenderem a determinados critérios, lança um
ValidationError. - Tratamento de Erros: Captura erros de validação e os registra com informações detalhadas. Outros tipos de erros também são tratados separadamente.
Boas Práticas e Armadilhas
- Sempre chame
super(message)primeiro, antes de usarthis. - Defina
namepara que os logs etoString()sejam legíveis;this.name = this.constructor.namefaz isso uma vez para toda uma hierarquia. - Prefira
instanceofem vez de comparações comerror.name— ele também corresponde a subclasses. - Relance o que você não reconhece. Um
catchque engole tudo (catch (e) {}) esconde bugs reais. Trate seus tipos conhecidos e usethrow errorpara o restante, como os exemplos demonstram. - Use
causepara envolver, não substituir. Quando você captura um erro de baixo nível e lança um de alto nível, passe{ cause: original }para que o stack trace e a causa raiz sejam preservados. - Não herde de
Errorpara controlar o fluxo. Erros são para condições excepcionais, não para ramificações normais.
Conclusão
Criar classes de erro personalizadas em JavaScript estendendo a classe Error é uma técnica poderosa para lidar com tipos específicos de erros de forma mais granular. Isso aumenta a clareza e a manutenibilidade do tratamento de erros no seu código, permitindo fornecer feedbacks e ações mais específicas com base em diferentes condições de erro. Esse método não apenas ajuda na depuração, mas também melhora a confiabilidade das suas aplicações, garantindo que cada tipo de erro seja capturado e tratado adequadamente.