Quero lançar algumas coisas no meu código JS e quero que elas sejam instância de Error, mas também quero que elas sejam outra coisa.
Em Python, normalmente, uma subclasse de Exception.
Qual é a coisa apropriada a fazer no JS?
fonte
Quero lançar algumas coisas no meu código JS e quero que elas sejam instância de Error, mas também quero que elas sejam outra coisa.
Em Python, normalmente, uma subclasse de Exception.
Qual é a coisa apropriada a fazer no JS?
O único campo padrão Erro que o objeto possui é a message
propriedade (Consulte MDN , ou EcmaScript Language Specification, seção 15.11) Todo o resto é específico da plataforma.
Ambientes mosts definir a stack
propriedade, mas fileName
e lineNumber
são praticamente inúteis para ser usado em herança.
Portanto, a abordagem minimalista é:
function MyError(message) {
this.name = 'MyError';
this.message = message;
this.stack = (new Error()).stack;
}
MyError.prototype = new Error; // <-- remove this if you do not
// want MyError to be instanceof Error
Você pode cheirar a pilha, desviar dela elementos indesejados e extrair informações como fileName e lineNumber, mas isso exige informações sobre a plataforma na qual o JavaScript está sendo executado atualmente. Na maioria dos casos, isso é desnecessário - e você pode fazê-lo post-mortem, se realmente quiser.
O Safari é uma exceção notável. Não há stack
propriedade, mas os throw
conjuntos de palavras - chave sourceURL
e as line
propriedades do objeto que está sendo lançado. É garantido que essas coisas estão corretas.
Os casos de teste que usei podem ser encontrados aqui: Comparação de objetos de erro feita por JavaScript .
this.name = 'MyError'
externa da função e alterá-la paraMyError.prototype.name = 'MyError'
.function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
MyError.prototype.constructor = MyError
também.this
, certo?No ES6:
fonte
fonte
var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
this.name = this.constructor.name;
lugar.Em resumo:
Se você estiver usando o ES6 sem transpilers :
Se você estiver usando o transpiler Babel :
Opção 1: use babel-plugin-transform-builtin-extend
Opção 2: faça você mesmo (inspirado nessa mesma biblioteca)
Se você estiver usando o ES5 puro :
Alternativa: use estrutura Classtrophobic
Explicação:
Por que estender a classe Error usando ES6 e Babel é um problema?
Porque uma instância do CustomError não é mais reconhecida como tal.
De fato, a partir da documentação oficial de Babel, você não pode estender qualquer built-in classes JavaScript , como
Date
,Array
,DOM
ouError
.O problema está descrito aqui:
E as outras respostas do SO?
Todas as respostas fornecidas corrigem o
instanceof
problema, mas você perde o erro regularconsole.log
:Considerando que, usando o método mencionado acima, você não apenas corrige o
instanceof
problema, mas também mantém o erro regularconsole.log
:fonte
class CustomError extends Error { /* ... */}
não lida corretamente com argumentos específicos do fornecedor (lineNumber
, etc), 'Estendendo erro em Javascript com sintaxe ES6' é específico da Babel, sua solução ES5 usaconst
e não lida com argumentos personalizados.console.log(new CustomError('test') instanceof CustomError);// false
era verdadeiro no momento da redação, mas agora foi resolvido. De fato, o problema vinculado na resposta foi resolvido e podemos testar o comportamento correto aqui , colando o código no REPL e vendo como ele é transpilado corretamente para instanciar com a cadeia de protótipo correta.Editar: Por favor, leia os comentários. Acontece que isso só funciona bem na V8 (Chrome / Node.JS). Minha intenção era fornecer uma solução entre navegadores, que funcionaria em todos os navegadores e fornecer rastreamento de pilha onde o suporte estivesse disponível.
Editar: Criei este Wiki da comunidade para permitir mais edições.
A solução para V8 (Chrome / Node.JS) funciona no Firefox e pode ser modificada para funcionar corretamente no IE. (veja o final da postagem)
Post original em "Mostre-me o código!"
Versão curta:
Eu mantenho
this.constructor.prototype.__proto__ = Error.prototype
dentro da função para manter todo o código juntos. Mas você também pode substituirthis.constructor
porUserError
e isso permite que você mova o código para fora da função, para que ele seja chamado apenas uma vez.Se você seguir esse caminho, ligue para essa linha antes de da primeira vez que jogar
UserError
.Essa ressalva não aplica a função, porque as funções são criadas primeiro, independentemente da ordem. Assim, você pode mover a função para o final do arquivo, sem problemas.
Compatibilidade do navegador
Funciona no Firefox e Chrome (e Node.JS) e cumpre todas as promessas.
Internet Explorer falha no seguinte
Os erros não precisam
err.stack
começar, então "não é minha culpa".Error.captureStackTrace(this, this.constructor)
não existe, então você precisa fazer outra coisa comotoString
deixa de existir quando você subclasseError
. Então você também precisa adicionar.O IE não considerará
UserError
um, ainstanceof Error
menos que você execute o seguinte algum tempo antes dethrow UserError
fonte
Error.call(this)
de fato, não está fazendo nada, pois retorna um erro ao invés de modificarthis
.UserError.prototype = Error.prototype
é enganoso. Isso não gera herança, isso os torna da mesma classe .Object.setPrototypeOf(this.constructor.prototype, Error.prototype)
é preferívelthis.constructor.prototype.__proto__ = Error.prototype
, pelo menos para os navegadores atuais.Para evitar o padrão para cada tipo diferente de erro, combinei a sabedoria de algumas das soluções em uma
createErrorType
função:Em seguida, você pode definir novos tipos de erro facilmente da seguinte maneira:
fonte
this.name = name;
?name
já está definido no protótipo, não é mais necessário. Eu removi isso. Obrigado!Em 2018 , acho que esse é o melhor caminho; que suporta IE9 + e navegadores modernos.
ATUALIZAÇÃO : veja este teste e repo para comparação em diferentes implementações.
Lembre-se também de que a
__proto__
propriedade está obsoleta e é amplamente usada em outras respostas.fonte
setPrototypeOf()
? Pelo menos de acordo com o MDN, geralmente é desencorajado usá-lo se você puder realizar a mesma coisa, apenas configurando a.prototype
propriedade no construtor (como você faz noelse
bloco para navegações que não possuemsetPrototypeOf
).setPrototypeOf
. Mas se você ainda precisar (como o OP solicita), use a metodologia incorporada. Como o MDN indica, essa é considerada a maneira correta de definir o protótipo de um objeto. Em outras palavras, o MDN diz que não altere o protótipo (pois afeta o desempenho e a otimização), mas se for necessário, usesetPrototypeOf
.CustomError.prototype = Object.create(Error.prototype)
). Além disso,Object.setPrototypeOf(CustomError, Error.prototype)
está configurando o protótipo do próprio construtor em vez de especificar o protótipo para novas instâncias deCustomError
. De qualquer forma, em 2016, acho que há realmente uma maneira melhor de estender erros, embora ainda esteja descobrindo como usá-lo junto com Babel: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/…CustomError.prototype = Object.create(Error.prototype)
também está mudando o protótipo. Você precisa alterá-lo, pois não há lógica de extensão / herança interna no ES5. Eu tenho certeza que o plugin babel que você mencionou faz coisas semelhantes.Object.setPrototypeOf
não faz sentido aqui, pelo menos não da maneira que você está usando: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Talvez você quisesse escreverObject.setPrototypeOf(CustomError.prototype, Error.prototype)
- isso faria um pouco mais de sentido (embora ainda não ofereça nenhum benefício em relação à simples configuraçãoCustomError.prototype
).Por uma questão de integridade - apenas porque nenhuma das respostas anteriores mencionou esse método - se você estiver trabalhando com o Node.js. e não precisar se preocupar com a compatibilidade do navegador, é fácil obter o efeito desejado com o recurso incorporado
inherits
doutil
módulo ( documentos oficiais aqui ).Por exemplo, suponha que você deseje criar uma classe de erro personalizada que use um código de erro como o primeiro argumento e a mensagem de erro como o segundo argumento:
arquivo custom-error.js :
Agora você pode instanciar e passar / lançar seu
CustomError
:Observe que, com esse trecho, o rastreamento da pilha terá o nome e a linha corretos do arquivo e a instância de erro terá o nome correto!
Isso acontece devido ao uso do
captureStackTrace
método, que cria umastack
propriedade no objeto de destino (nesse caso, oCustomError
instanciado). Para mais detalhes sobre como funciona, consulte a documentação aqui .fonte
this.message = this.message;
isso está errado ou ainda existem coisas malucas que eu não sei sobre JS?A resposta altamente votada da Crescent Fresh é enganosa. Embora seus avisos sejam inválidos, há outras limitações que ele não aborda.
Primeiro, o raciocínio no parágrafo "Advertências:" de Crescent não faz sentido. A explicação implica que a codificação "um monte de if (instância de erro do MyError) mais ..." é de alguma forma onerosa ou detalhada em comparação com várias instruções catch. Várias instâncias de instruções em um único bloco de captura são tão concisas quanto várias instruções de captura - código limpo e conciso sem truques. Essa é uma ótima maneira de emular o ótimo tratamento de erros específico de subtipo de lançamento de Java.
WRT "aparece que a propriedade de mensagem da subclasse não é definida", esse não é o caso se você usar uma subclasse de erro construída corretamente. Para criar sua própria subclasse ErrorX Error, copie o bloco de código começando com "var MyError =", alterando a palavra "MyError" para "ErrorX". (Se você deseja adicionar métodos personalizados à sua subclasse, siga o texto de exemplo).
A limitação real e significativa da subclasse de erro de JavaScript é que, para implementações ou depuradores de JavaScript que rastreiam e relatam o rastreamento de pilha e o local da instanciação, como o FireFox, um local em sua própria implementação da subclasse Error será registrado como o ponto de instanciação do classe, enquanto que se você usasse um erro direto, seria o local em que você executaria "novo erro (...)"). Os usuários do IE provavelmente nunca notariam, mas os usuários do Fire Bug no FF verão valores inúteis de nome de arquivo e número de linha relatados juntamente com esses erros e terão que fazer uma busca detalhada no rastreamento da pilha do elemento 1 para encontrar o local real da instanciação.
fonte
Crescent Fresh's
foi excluída!Que tal esta solução?
Em vez de lançar seu erro personalizado usando:
Você agruparia o objeto Error (como um Decorator):
Isso garante que todos os atributos estejam corretos, como a pilha, fileName lineNumber, etc.
Tudo o que você precisa fazer é copiar os atributos ou definir getters para eles. Aqui está um exemplo usando getters (IE9):
fonte
new MyErr (arg1, arg2, new Error())
e no construtor MyErr, usamosObject.assign
para atribuir as propriedades do último arg parathis
Como algumas pessoas disseram, é bastante fácil com o ES6:
Então, eu tentei isso no meu aplicativo (Angular, TypeScript) e simplesmente não funcionou. Depois de algum tempo, descobri que o problema vem do TypeScript: O
Consulte https://github.com/Microsoft/TypeScript/issues/13965
É muito perturbador, porque se você fizer:
No nó ou diretamente no seu navegador, ele exibirá:
Custom error
Tente executar isso com o Typecript em seu projeto no playground Typcript, ele exibirá
Basic error
...A solução é fazer o seguinte:
fonte
Minha solução é mais simples do que as outras respostas fornecidas e não tem desvantagens.
Ele preserva a cadeia de protótipos Error e todas as propriedades no Error sem precisar de conhecimento específico delas. Foi testado no Chrome, Firefox, Node e IE11.
A única limitação é uma entrada extra na parte superior da pilha de chamadas. Mas isso é facilmente ignorado.
Aqui está um exemplo com dois parâmetros personalizados:
Exemplo de uso:
Para ambientes que requerem um polyfil de setPrototypeOf:
fonte
throw CustomError('err')
vez dethrow new CustomError('err')
No exemplo acima
Error.apply
(tambémError.call
) não faz nada por mim (Firefox 3.6 / Chrome 5). Uma solução alternativa que eu uso é:fonte
No Node, como já foi dito, é simples:
fonte
Eu só quero adicionar ao que outros já declararam:
Para garantir que a classe de erro customizada seja exibida corretamente no rastreamento da pilha, é necessário definir a propriedade name da classe de erro customizada como a propriedade name da classe de erro customizada. É isso que eu quero dizer:
Portanto, o exemplo completo seria:
Quando tudo estiver pronto, você lança sua nova exceção e fica assim (tentei preguiçosamente isso nas ferramentas de desenvolvimento do chrome):
fonte
Meus 2 centavos:
Por que outra resposta?
a) Porque o acesso à
Error.stack
propriedade (como em algumas respostas) tem uma grande penalidade de desempenho.b) Porque é apenas uma linha.
c) Como a solução em https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error parece não preservar as informações da pilha.
exemplo de uso
http://jsfiddle.net/luciotato/xXyeB/
O que isso faz?
this.__proto__.__proto__
éMyError.prototype.__proto__
, portanto, está configurando__proto__
FOR ALL INSTANCES do MyError como um erro específico recém-criado. Ele mantém as propriedades e métodos da classe MyError e também coloca as novas propriedades Error (incluindo .stack) na__proto__
cadeia.Problema óbvio:
Você não pode ter mais de uma instância do MyError com informações úteis sobre a pilha.
Não use esta solução se você não entender completamente o que
this.__proto__.__proto__=
faz.fonte
Como as Exceções de JavaScript são difíceis de subclasses, eu não subclasses. Acabei de criar uma nova classe de exceção e uso um erro dentro dela. Altero a propriedade Error.name para que pareça minha exceção personalizada no console:
A nova exceção acima pode ser lançada como um erro regular e funcionará conforme o esperado, por exemplo:
Advertência: o rastreamento da pilha não é perfeito, pois o levará para onde o novo erro é criado e não para onde você lança. Isso não é muito importante no Chrome, pois fornece um rastreamento completo da pilha diretamente no console. Mas é mais problemático no Firefox, por exemplo.
fonte
m = new InvalidInputError(); dontThrowMeYet(m);
m = new ...
entãoPromise.reject(m)
. Não é necessário, mas o código é mais fácil de ler.Como apontado na resposta de Mohsen, no ES6 é possível estender erros usando classes. É muito mais fácil e o comportamento deles é mais consistente com erros nativos ... mas, infelizmente, não é uma questão simples usá-lo no navegador se você precisar oferecer suporte a navegadores anteriores ao ES6. Veja abaixo algumas notas sobre como isso pode ser implementado, mas, enquanto isso, sugiro uma abordagem relativamente simples que incorpore algumas das melhores sugestões de outras respostas:
No ES6, é tão simples quanto:
... e você pode detectar o suporte para as classes ES6 com
try {eval('class X{}')
, mas receberá um erro de sintaxe se tentar incluir a versão ES6 em um script carregado por navegadores mais antigos. Portanto, a única maneira de oferecer suporte a todos os navegadores seria carregar um script separado dinamicamente (por exemplo, via AJAX oueval()
) para navegadores que suportam o ES6. Outra complicação é queeval()
não há suporte para todos os ambientes (devido às Políticas de segurança de conteúdo), o que pode ou não ser uma consideração para o seu projeto.Então, por enquanto, ou a primeira abordagem acima ou simplesmente usando
Error
direto, sem tentar estendê-la, parece ser o melhor que pode ser feito praticamente para o código que precisa oferecer suporte a navegadores não ES6.Há uma outra abordagem que algumas pessoas podem querer considerar, que é usar
Object.setPrototypeOf()
quando disponível para criar um objeto de erro que é uma instância do seu tipo de erro personalizado, mas que se parece e se comporta mais como um erro nativo no console (graças à resposta de Ben pela recomendação). Aqui está minha opinião sobre essa abordagem: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Mas, como um dia poderemos usar o ES6, pessoalmente não tenho certeza de que a complexidade dessa abordagem vale a pena.fonte
A maneira de fazer isso corretamente é retornar o resultado da aplicação do construtor, além de definir o protótipo da maneira javascripty complicada usual:
Os únicos problemas com essa maneira de fazê-lo neste momento (eu iteramos um pouco) são que
stack
emessage
não estão incluídasMyError
eO primeiro problema pode ser corrigido através da iteração de todas as propriedades não enumeráveis de erro, usando o truque nesta resposta: É possível obter os nomes de propriedades herdadas não enumeráveis de um objeto? , mas isso não é suportado por ie <9. O segundo problema pode ser resolvido arrancando essa linha no rastreamento da pilha, mas não tenho certeza de como fazer isso com segurança (talvez apenas removendo a segunda linha de e.stack.toString () ??).
fonte
O trecho mostra tudo.
fonte
Eu daria um passo atrás e consideraria por que você quer fazer isso? Eu acho que o objetivo é lidar com diferentes erros de maneira diferente.
Por exemplo, em Python, você pode restringir a instrução catch apenas para catch
MyValidationError
e talvez você queira fazer algo semelhante em javascript.Você não pode fazer isso em javascript. Só haverá um bloco de captura. Você deveria usar uma instrução if no erro para determinar seu tipo.
catch(e) { if(isMyValidationError(e)) { ... } else { // maybe rethrow? throw e; } }
Eu acho que, em vez disso, jogaria um objeto bruto com um tipo, mensagem e outras propriedades que você achar melhor.
E quando você pegar o erro:
fonte
error.stack
, ferramentas padrão não vai funcionar com ele, etc etc. A melhor forma seria a de adicionar propriedades a uma instância de erro, por exemplovar e = new Error(); e.type = "validation"; ...
Decorador de erro personalizado
Isso é baseado na resposta de George Bailey , mas amplia e simplifica a ideia original. Está escrito em CoffeeScript, mas é fácil de converter para JavaScript. A idéia é estender o erro personalizado de Bailey com um decorador que o envolve, permitindo que você crie novos erros personalizados com facilidade.
Nota: Isso funcionará apenas na V8. Não há suporte para
Error.captureStackTrace
outros ambientes.Definir
O decorador pega um nome para o tipo de erro e retorna uma função que recebe uma mensagem de erro e inclui o nome do erro.
Usar
Agora é simples criar novos tipos de erro.
Por diversão, agora você pode definir uma função que lança a
SignatureError
se for chamada com muitos argumentos.Isso foi testado muito bem e parece funcionar perfeitamente no V8, mantendo o rastreio, a posição etc.
Nota: O uso
new
é opcional ao construir um erro personalizado.fonte
se você não se importa com performances por erros, este é o menor que você pode fazer
você pode usá-lo sem o novo apenas MyError (mensagem)
Ao alterar o protótipo após o construtor Error ser chamado, não precisamos definir o callstack e a mensagem
fonte
Mohsen tem uma ótima resposta acima no ES6 que define o nome, mas se você estiver usando o TypeScript ou se estiver vivendo no futuro, espero que esta proposta para campos de classe pública e privada tenha passado do estágio 3 como uma proposta e a tenha feito no estágio 4, como parte do ECMAScript / JavaScript, convém saber que isso é um pouco mais curto. O estágio 3 é o local em que os navegadores começam a implementar os recursos. Portanto, se o seu navegador suportar, o código abaixo poderá funcionar. (Testado no novo navegador Edge v81, parece funcionar bem). Esteja avisado de que este é um recurso instável no momento e deve ser usado com cautela e você deve sempre verificar o suporte do navegador em recursos instáveis. Esta postagem é principalmente para os futuros moradores quando os navegadores puderem suportar isso. Para verificar o suporte, verifique o MDNe posso usar . Atualmente, ele possui 66% de suporte no mercado de navegadores, o que está chegando lá, mas não é tão bom; se você realmente quiser usá-lo agora e não quiser esperar, use um transpiler como Babel ou algo como TypeScript .
Compare isso com um erro sem nome que, quando lançado, não registrará seu nome.
fonte