Quando desenvolvemos alguma coisa, muitas vezes precisamos de nossas próprias classes de erro para refletir coisas específicas que podem errar em nossas tarefas. Para erros nas operações de rede, podemos precisar HttpError
, para operações de banco de dados , para operações de pesquisa NotFoundError
, etc.
Os nossos erros devem suportar propriedades de erro básicas, comomessage
,name
e, de preferência,stack
. Mas eles também podem ter outras propriedades, por exemplo, objetosHttpError
Pode ter uma propriedadestatusCode
com um valor como ou403
ou .
javascript permite que você use throw
Com qualquer argumento, então tecnicamente nossas aulas de erro personalizadas não precisam herdar de Error
. Mas se herdarmos, então é possível usar obj instanceof Error
para identificar erros de objetos. Então é melhor herdar dele.
Como o aplicativo cresce, nossos próprios erros formam naturalmente uma hierarquia. Por exemplo, HttpTimeoutError
pode herdar de HttpError
e assim por diante.
Erro de extensão
Como um exemplo, considere uma função readUser(json)
Que deve ler JSON com os dados do usuário.
Aqui está um exemplo de como um válido:
let json = `{ "name": "John", "age": 30 }`;
Internamente, usaremos JSON.parse
. Se você receber mal formado, então derrame SyntaxError
. Mas mesmo se está sintaticamente correto, isso não significa que seja um usuário válido, certo? Você pode perder os dados necessários. Por exemplo, você pode não ter propriedades de nome e idade que são essenciais para nossos usuários.
Nossa função readUser(json)
não só lerá JSON, mas verificará (“” Validar “) os dados. Se não houver campos obrigatórios, ou o formato estiver incorreto, ele será um erro. E isso não é um” syntaxError “, porque os dados estão sintaticamente corretos, mas outro tipo de erro. Vamos chamá-lo ValidationError
e criar uma classe para ela. Um erro desse tipo também deve trazer as informações sobre o campo mineral.
Nossa classe ValidationError
deve herdar da classe incorporada Error
esse tipo é incorporado, mas aqui está o seu código aproximado para que possamos entender o que estamos estendendo:
Agora vamos herdar ValidationError
e prove em ação:
class ValidationError extends Error { constructor(message) { super(message); // (1) this.name = "ValidationError"; // (2) }}function test() { throw new ValidationError("Vaya!");}try { test();} catch(err) { alert(err.message); // Vaya! alert(err.name); // ValidationError alert(err.stack); // una lista de llamadas anidadas con números de línea para cada una}
nota: na linha (1)
Nós chamamos o Pai Builder. JavaScript requer que chamemos super
no construtor filho, por isso é obrigatório. O construtor pai define a propriedade message
.
O construtor principal também define a propriedade name
no "Error"
, portanto, na linha (2)
redefina para o valor correto.
Tente usá-lo em readUser(json)
class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; }}// Usofunction readUser(json) { let user = JSON.parse(json); if (!user.age) { throw new ValidationError("Sin campo: age"); } if (!user.name) { throw new ValidationError("Sin campo: name"); } return user;}// Ejemplo de trabajo con try..catchtry { let user = readUser('{ "age": 25 }');} catch (err) { if (err instanceof ValidationError) { alert("Dato inválido: " + err.message); // Dato inválido: sin campo: nombre } else if (err instanceof SyntaxError) { // (*) alert("Error de sintaxis JSON: " + err.message); } else { throw err; // error desconocido, vuelva a lanzarlo (**) }}
O bloco try..catch
No código anterior lida com a nossa ValidationError
como o SyntaxError
Built-in JSON.parse
Note Como usamos para verificar o tipo de erro específico no (*)
.
Também poderíamos olhar para err.name
, assim:
// ...// en lugar de (err instanceof SyntaxError)} else if (err.name == "SyntaxError") { // (*)// ...
a versão instanceof
é muito melhor, porque em o futuro vamos estender Faremos subtipos dele, como PropertyRequiredError
. E controle instanceof
continuará a trabalhar para novas classes herdadas. Então isso vai para o futuro.
Também é importante que, se catch
Encontre um erro desconhecido, então ele a re-jogá-lo na linha
. O blococatch
Sabe apenas como manipular erros de validação e sintaxe, outros tipos (devido a um erro tipográfico no código ou outros estranhos) deve falhar.
adicional herança
classe ValidationError
é muito genérico. Muitas coisas podem dar errado.A propriedade pode estar ausente ou pode estar em um formato incorreto (como um valor de string para age
). Vamos fazer uma classe mais concreta PropertyRequiredError
, exatamente para propriedades ausentes. Ele terá informações adicionais sobre a propriedade ausente.
class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; }}class PropertyRequiredError extends ValidationError { constructor(property) { super("Sin propiedad: " + property); this.name = "PropertyRequiredError"; this.property = property; }}// Usofunction readUser(json) { let user = JSON.parse(json); if (!user.age) { throw new PropertyRequiredError("age"); } if (!user.name) { throw new PropertyRequiredError("name"); } return user;}// Ejemplo de trabajo con try..catchtry { let user = readUser('{ "age": 25 }');} catch (err) { if (err instanceof ValidationError) { alert("Dato inválido: " + err.message); // Dato inválido: Sin propiedad: name alert(err.name); // PropertyRequiredError alert(err.property); // name } else if (err instanceof SyntaxError) { alert("Error de sintaxis JSON: " + err.message); } else { throw err; // error desconocido, vuelva a lanzarlo }}
a nova classe PropertyRequiredError
é fácil de usar: só precisamos passar o nome da propriedade: new PropertyRequiredError(property)
. O message
é gerado pelo construtor.
Por favor, note que this.name
no construtor PropertyRequiredError
é atribuído manualmente novamente. Isso pode se tornar um pouco tedioso: Atribuir this.name = <class name>
em cada classe de erro personalizada. Podemos evitá-lo, tornando nosso próprio “erro básico” que atribui this.name = this.constructor.name
. E, em seguida, herda todos os nossos erros personalizados.
Vamos chamá-lo MyError
Aqui está o código com MyError
e outras classes personalizadas, erro simplificado:
class MyError extends Error { constructor(message) { super(message); this.name = this.constructor.name; }}class ValidationError extends MyError { }class PropertyRequiredError extends ValidationError { constructor(property) { super("sin propiedad: " + property); this.property = property; }}// name es incorrectoalert( new PropertyRequiredError("campo").name ); // PropertyRequiredError
Agora os erros personalizados são muito mais curtos, especialmente ValidationError
, já que excluímos a linha "this.name = ..."
no construtor.
incorporado de exceções
a finalidade da função readUser
No código anterior é “Leia os dados do usuário”. Pode haver diferentes tipos de erros no processo. Neste momento temos SyntaxError
e ValidationError
, mas no futuro a função readUser
pode Cresça e provavelmente gere outros tipos de erros.
O código que chama readUser
deve lidar com esses erros. Neste momento, usa vários if
no bloco catch
, que confirma a classe e lidar com os erros conhecidos e lançar os estranhos novamente.
o esquema é assim:
try { ... readUser() // la fuente potencial de error ...} catch (err) { if (err instanceof ValidationError) { // manejar errores de validación } else if (err instanceof SyntaxError) { // manejar errores de sintaxis } else { throw err; // error desconocido, vuelva a lanzarlo }}
No código anterior, podemos ver dois tipos de erros, mas pode haver mais.
Se a função readUser
gera vários tipos de erros, então devemos Pergunte-se: Nós realmente queremos verificar todos os tipos de erro um por um a cada vez?
Muitas vezes, a resposta é “não”: nós gostaríamos de ser “um nível acima de tudo isso”. Nós só queremos saber se houve um “erro de leitura de dados”: por que aconteceu exatamente é quase irrelevante (a mensagem de erro descreve). Ou melhor ainda, gostaríamos de ter uma maneira de obter os detalhes do erro, Mas apenas se necessário.
A técnica que descrevemos aqui é chamada “Exceções de embalagem”.
- vamos criar uma nova classe
ReadError
representar um erro genérico de “leitura de dados”. - a função
readUser
detectará os erros de leitura de dados que ocorrem dentro dela, comoValidationError
eSyntaxError
e gerar umReadError
- o objeto
ReadError
Manterá a referência de erro original em sua propriedadecause
Então, o código que chama readUser
só terá que verificar ReadError
, nem todos os tipos de erros de leitura D Atos. E se você precisar de mais detalhes de um erro, poderá verificar sua propriedade cause
.
Aqui está o código que define ReadError
e demonstra seu uso em readUser
e try..catch
:
class ReadError extends Error { constructor(message, cause) { super(message); this.cause = cause; this.name = 'ReadError'; }}class ValidationError extends Error { /*...*/ }class PropertyRequiredError extends ValidationError { /* ... */ }function validateUser(user) { if (!user.age) { throw new PropertyRequiredError("age"); } if (!user.name) { throw new PropertyRequiredError("name"); }}function readUser(json) { let user; try { user = JSON.parse(json); } catch (err) { if (err instanceof SyntaxError) { throw new ReadError("Error de sintaxis", err); } else { throw err; } } try { validateUser(user); } catch (err) { if (err instanceof ValidationError) { throw new ReadError("Error de validación", err); } else { throw err; } }}try { readUser('{json malo}');} catch (e) { if (e instanceof ReadError) { alert(e); // Error original: SyntaxError: inesperado token b en JSON en la posición 1 alert("Error original: " + e.cause); } else { throw e; }}
No código anterior, readUser
funciona Exatamente como se descrevem: detecta erros de sintaxe e validação e lança os erros ReadError
(erros desconhecidos são gerados como de costume).
Então, o código externo verifica instanceof ReadError
e é isso. Não é necessário listar todos os tipos de erro possíveis.
O foco é chamado de “exceções de embalagem”, porque tomamos exceções “baixo nível” e “ajuste” em ReadError
O que é mais abstrato. É amplamente utilizado em programação orientada a objetos.
Resumo
- Podemos herdar de
Error
e outras classes de erro gentilmente embutidas. Nós só precisamos cuidar da propriedadename
e não se esqueça de chamarsuper
. - podemos usar para verificar erros específicos. Também funciona com herança.Mas às vezes temos um objeto de erro que vem de uma biblioteca de terceiros e não há maneira fácil de obter sua classe. Em seguida, a propriedade
name
pode ser usada para esses controles. - Exceções O pacote é uma técnica generalizada: uma função manipula exceções de baixo nível e cria erros de alto nível em Lugar de vários erros de baixo nível. As exceções de baixo nível são às vezes convertidas em propriedades desse objeto como
err.cause
nos exemplos anteriores, mas isso não é estritamente necessário.