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?
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?
Respostas:
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 .
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:
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
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 :
class CustomError extends Error { /* ... */}
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)
function CustomError(...args) {
const instance = Reflect.construct(Error, args);
Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
return instance;
}
CustomError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});
Reflect.setPrototypeOf(CustomError, Error);
Se você estiver usando o ES5 puro :
function CustomError(message, fileName, lineNumber) {
var instance = new Error(message, fileName, lineNumber);
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
return instance;
}
CustomError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});
if (Object.setPrototypeOf){
Object.setPrototypeOf(CustomError, Error);
} else {
CustomError.__proto__ = Error;
}
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.
class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false
De fato, a partir da documentação oficial de Babel, você não pode estender qualquer built-in classes JavaScript , como Date
, Array
, DOM
ou Error
.
O problema está descrito aqui:
E as outras respostas do SO?
Todas as respostas fornecidas corrigem o instanceof
problema, mas você perde o erro regular console.log
:
console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵ at CustomError (<anonymous>:4:19)↵ at <anonymous>:1:5"}
Considerando que, usando o método mencionado acima, você não apenas corrige o instanceof
problema, mas também mantém o erro regular console.log
:
console.log(new CustomError('test'));
// output:
// Error: test
// at CustomError (<anonymous>:2:32)
// at <anonymous>:1:5
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 usa const
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)
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype // Make this an instanceof Error.
Error.call(this) // Does not seem necessary. Perhaps remove this line?
Error.captureStackTrace(this, this.constructor) // Creates the this.stack getter
this.name = this.constructor.name; // Used to cause messages like "UserError: message" instead of the default "Error: message"
this.message = message; // Used to set the message
}
Post original em "Mostre-me o código!"
Versão curta:
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = message
}
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
por UserError
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 como
if(Error.captureStackTrace) // AKA if not IE
Error.captureStackTrace(this, this.constructor)
toString
deixa de existir quando você subclasse Error
. Então você também precisa adicionar.
else
this.toString = function () { return this.name + ': ' + this.message }
O IE não considerará UserError
um, a instanceof Error
menos que você execute o seguinte algum tempo antes dethrow UserError
UserError.prototype = Error.prototype
Error.call(this)
de fato, não está fazendo nada, pois retorna um erro ao invés de modificar this
.
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ível this.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:
function createErrorType(name, init) {
function E(message) {
if (!Error.captureStackTrace)
this.stack = (new Error()).stack;
else
Error.captureStackTrace(this, this.constructor);
this.message = message;
init && init.apply(this, arguments);
}
E.prototype = new Error();
E.prototype.name = name;
E.prototype.constructor = E;
return E;
}
Em seguida, você pode definir novos tipos de erro facilmente da seguinte maneira:
var NameError = createErrorType('NameError', function (name, invalidChar) {
this.message = 'The name ' + name + ' may not contain ' + invalidChar;
});
var UnboundError = createErrorType('UnboundError', function (variableName) {
this.message = 'Variable ' + variableName + ' is not bound';
});
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.
function CustomError(message) {
Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: 'CustomError'
});
Object.defineProperty(this, 'message', {
enumerable: false,
writable: true,
value: message
});
if (Error.hasOwnProperty('captureStackTrace')) { // V8
Error.captureStackTrace(this, CustomError);
} else {
Object.defineProperty(this, 'stack', {
enumerable: false,
writable: false,
value: (new Error(message)).stack
});
}
}
if (typeof Object.setPrototypeOf === 'function') {
Object.setPrototypeOf(CustomError.prototype, Error.prototype);
} else {
CustomError.prototype = Object.create(Error.prototype, {
constructor: { value: CustomError }
});
}
Lembre-se também de que a __proto__
propriedade está obsoleta e é amplamente usada em outras respostas.
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 no else
bloco para navegações que não possuem setPrototypeOf
).
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, use setPrototypeOf
.
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 de CustomError
. 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 escrever Object.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ção CustomError.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
do util
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 :
'use strict';
var util = require('util');
function CustomError(code, message) {
Error.captureStackTrace(this, CustomError);
this.name = CustomError.name;
this.code = code;
this.message = message;
}
util.inherits(CustomError, Error);
module.exports = CustomError;
Agora você pode instanciar e passar / lançar seu CustomError
:
var CustomError = require('./path/to/custom-error');
// pass as the first argument to your callback
callback(new CustomError(404, 'Not found!'));
// or, if you are working with try/catch, throw it
throw new CustomError(500, 'Server Error!');
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 uma stack
propriedade no objeto de destino (nesse caso, o CustomError
instanciado). Para mais detalhes sobre como funciona, consulte a documentação aqui .
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.
Crescent Fresh's
foi excluída!
Que tal esta solução?
Em vez de lançar seu erro personalizado usando:
throw new MyError("Oops!");
Você agruparia o objeto Error (como um Decorator):
throw new MyError(Error("Oops!"));
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):
function MyError(wrapped)
{
this.wrapped = wrapped;
this.wrapped.name = 'MyError';
}
function wrap(attr)
{
Object.defineProperty(MyError.prototype, attr, {
get: function()
{
return this.wrapped[attr];
}
});
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;
wrap('name');
wrap('message');
wrap('stack');
wrap('fileName');
wrap('lineNumber');
wrap('columnNumber');
MyError.prototype.toString = function()
{
return this.wrapped.toString();
};
new MyErr (arg1, arg2, new Error())
e no construtor MyErr, usamos Object.assign
para atribuir as propriedades do último arg parathis
Como algumas pessoas disseram, é bastante fácil com o ES6:
class CustomError extends Error { }
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:
class CustomError extends Error {}
try {
throw new CustomError()
} catch(e) {
if (e instanceof CustomError) {
console.log('Custom error');
} else {
console.log('Basic error');
}
}
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:
class CustomError extends Error {
// we have to do the following because of: https://github.com/Microsoft/TypeScript/issues/13965
// otherwise we cannot use instanceof later to catch a given type
public __proto__: Error;
constructor(message?: string) {
const trueProto = new.target.prototype;
super(message);
this.__proto__ = trueProto;
}
}
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:
function CustomError(message, param1, param2) {
var err = new Error(message);
Object.setPrototypeOf(err, CustomError.prototype);
err.param1 = param1;
err.param2 = param2;
return err;
}
CustomError.prototype = Object.create(
Error.prototype,
{name: {value: 'CustomError', enumerable: false}}
);
Exemplo de uso:
try {
throw new CustomError('Something Unexpected Happened!', 1234, 'neat');
} catch (ex) {
console.log(ex.name); //CustomError
console.log(ex.message); //Something Unexpected Happened!
console.log(ex.param1); //1234
console.log(ex.param2); //neat
console.log(ex.stack); //stacktrace
console.log(ex instanceof Error); //true
console.log(ex instanceof CustomError); //true
}
Para ambientes que requerem um polyfil de setPrototypeOf:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
obj.__proto__ = proto;
return obj;
};
throw CustomError('err')
vez dethrow new CustomError('err')
No exemplo acima Error.apply
(também Error.call
) não faz nada por mim (Firefox 3.6 / Chrome 5). Uma solução alternativa que eu uso é:
function MyError(message, fileName, lineNumber) {
var err = new Error();
if (err.stack) {
// remove one stack level:
if (typeof(Components) != 'undefined') {
// Mozilla:
this.stack = err.stack.substring(err.stack.indexOf('\n')+1);
}
else if (typeof(chrome) != 'undefined' || typeof(process) != 'undefined') {
// Google Chrome/Node.js:
this.stack = err.stack.replace(/\n[^\n]*/,'');
}
else {
this.stack = err.stack;
}
}
this.message = message === undefined ? err.message : message;
this.fileName = fileName === undefined ? err.fileName : fileName;
this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
}
MyError.prototype = new Error();
MyError.prototype.constructor = MyError;
MyError.prototype.name = 'MyError';
No Node, como já foi dito, é simples:
class DumbError extends Error {
constructor(foo = 'bar', ...params) {
super(...params);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, DumbError);
}
this.name = 'DumbError';
this.foo = foo;
this.date = new Date();
}
}
try {
let x = 3;
if (x < 10) {
throw new DumbError();
}
} catch (error) {
console.log(error);
}
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:
CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';
Portanto, o exemplo completo seria:
var CustomError = function(message) {
var err = new Error(message);
err.name = 'CustomError';
this.name = err.name;
this.message = err.message;
//check if there is a stack property supported in browser
if (err.stack) {
this.stack = err.stack;
}
//we should define how our toString function works as this will be used internally
//by the browser's stack trace generation function
this.toString = function() {
return this.name + ': ' + this.message;
};
};
CustomError.prototype = new Error();
CustomError.prototype.name = 'CustomError';
Quando tudo estiver pronto, você lança sua nova exceção e fica assim (tentei preguiçosamente isso nas ferramentas de desenvolvimento do chrome):
CustomError: Stuff Happened. GASP!
at Error.CustomError (<anonymous>:3:19)
at <anonymous>:2:7
at Object.InjectedScript._evaluateOn (<anonymous>:603:39)
at Object.InjectedScript._evaluateAndWrap (<anonymous>:562:52)
at Object.InjectedScript.evaluate (<anonymous>:481:21)
Meus 2 centavos:
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.
//MyError class constructor
function MyError(msg){
this.__proto__.__proto__ = Error.apply(null, arguments);
};
exemplo de uso
http://jsfiddle.net/luciotato/xXyeB/
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.
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.
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:
var InvalidInputError = function(message) {
var error = new Error(message);
error.name = 'InvalidInputError';
return error;
};
A nova exceção acima pode ser lançada como um erro regular e funcionará conforme o esperado, por exemplo:
throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string
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.
m = new InvalidInputError(); dontThrowMeYet(m);
m = new ...
então Promise.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:
function CustomError(message) {
//This is for future compatibility with the ES6 version, which
//would display a similar message if invoked without the
//`new` operator.
if (!(this instanceof CustomError)) {
throw new TypeError("Constructor 'CustomError' cannot be invoked without 'new'");
}
this.message = message;
//Stack trace in V8
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomError);
}
else this.stack = (new Error).stack;
}
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.name = 'CustomError';
No ES6, é tão simples quanto:
class CustomError extends Error {}
... 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 ou eval()
) 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.
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:
function MyError() {
var tmp = Error.apply(this, arguments);
tmp.name = this.name = 'MyError'
this.stack = tmp.stack
this.message = tmp.message
return this
}
var IntermediateInheritor = function() {}
IntermediateInheritor.prototype = Error.prototype;
MyError.prototype = new IntermediateInheritor()
var myError = new MyError("message");
console.log("The message is: '"+myError.message+"'") // The message is: 'message'
console.log(myError instanceof Error) // true
console.log(myError instanceof MyError) // true
console.log(myError.toString()) // MyError: message
console.log(myError.stack) // MyError: message \n
// <stack trace ...>
Os únicos problemas com essa maneira de fazê-lo neste momento (eu iteramos um pouco) são que
stack
e message
não estão incluídas MyError
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 () ??).
O trecho mostra tudo.
function add(x, y) {
if (x && y) {
return x + y;
} else {
/**
*
* the error thrown will be instanceof Error class and InvalidArgsError also
*/
throw new InvalidArgsError();
// throw new Invalid_Args_Error();
}
}
// Declare custom error using using Class
class Invalid_Args_Error extends Error {
constructor() {
super("Invalid arguments");
Error.captureStackTrace(this);
}
}
// Declare custom error using Function
function InvalidArgsError(message) {
this.message = `Invalid arguments`;
Error.captureStackTrace(this);
}
// does the same magic as extends keyword
Object.setPrototypeOf(InvalidArgsError.prototype, Error.prototype);
try{
add(2)
}catch(e){
// true
if(e instanceof Error){
console.log(e)
}
// true
if(e instanceof InvalidArgsError){
console.log(e)
}
}
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.
catch (MyValidationError e) {
....
}
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.
throw { type: "validation", message: "Invalid timestamp" }
E quando você pegar o erro:
catch(e) {
if(e.type === "validation") {
// handle error
}
// re-throw, or whatever else
}
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"; ...
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.
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.
CoreError = (@message) ->
@constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace @, @constructor
@name = @constructor.name
BaseError = (type) ->
(message) -> new CoreError "#{ type }Error: #{ message }"
Agora é simples criar novos tipos de erro.
StorageError = BaseError "Storage"
SignatureError = BaseError "Signature"
Por diversão, agora você pode definir uma função que lança a SignatureError
se for chamada com muitos argumentos.
f = -> throw SignatureError "too many args" if arguments.length
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.
se você não se importa com performances por erros, este é o menor que você pode fazer
Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
const error = new Error(message)
Object.setPrototypeOf(error, MyError.prototype);
return error
}
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
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 .
class EOFError extends Error {
name="EOFError"
}
throw new EOFError("Oops errored");
Compare isso com um erro sem nome que, quando lançado, não registrará seu nome.
class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");
this.name = 'MyError'
externa da função e alterá-la paraMyError.prototype.name = 'MyError'
.