O que é uma boa maneira de estender o erro em JavaScript?

Eu quero jogar algumas coisas no meu código JS e eu quero que elas sejam exemplo de erro, mas eu também quero que elas sejam outra coisa.

Em Python, normalmente, seria uma subclass Exception.

Qual é a coisa apropriada a fazer no JS?

O único campo padrão que o object Error possui é a propriedade da message . (Veja MDN , ou EcmaScript Language Specification, seção 15.11) Todo o resto é específico da plataforma.

A maioria dos ambientes define a propriedade stack , mas fileName e lineNumber são praticamente inúteis para serem usados ​​em inheritance.

Então, 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ê poderia farejar a pilha, desviar elementos indesejados dela e extrair informações como fileName e lineNumber, mas isso exige informações sobre a plataforma em que o JavaScript está sendo executado. A maioria dos casos é desnecessária - e você pode fazê-lo em post-mortem, se você realmente quiser.

O Safari é uma exceção notável. Não há propriedade stack , mas a palavra-chave throw define sourceURL e as propriedades de line do object que está sendo lançado. Essas coisas estão garantidas para estarem corretas.

Os casos de teste que usei podem ser encontrados aqui: JavaScript self-made Comparação de objects de erro .

No ES6:

 class MyError extends Error { constructor(message) { super(message); this.name = 'MyError'; } } 

fonte

Editar: Por favor, leia os comentários. Acontece que isso só funciona bem no V8 (Chrome / Node.JS) Minha intenção era fornecer uma solução entre navegadores, que funcionasse em todos os navegadores, e fornecer rastreamento de pilha onde o suporte está lá.

Edit: eu fiz este Wiki da Comunidade para permitir mais edição.

Solução para V8 (Chrome / Node.JS), funciona no Firefox e pode ser modificado para funcionar principalmente corretamente no IE. (veja o final do post)

 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 function para manter todo o código junto. Mas você também pode replace this.constructor com UserError e isso permite que você mova o código para fora da function, para que ele seja chamado apenas uma vez.

Se você seguir essa rota, certifique-se de chamar essa linha antes da primeira vez que lançar o UserError .

Essa ressalva não aplica a function, porque as funções são criadas primeiro, não importa a ordem. Assim, você pode mover a function para o final do arquivo, sem nenhum problema.

Compatibilidade do Navegador

Funciona no Firefox e Chrome (e Node.JS) e preenche todas as promises.

Internet Explorer falha no seguinte

  • Erros não têm err.stack para começar, então “não é minha culpa”.

  • Error.captureStackTrace(this, this.constructor) não existe, então você precisa fazer algo como

     if(Error.captureStackTrace) // AKA if not IE Error.captureStackTrace(this, this.constructor) 
  • toString deixa de existir quando você subclass Error . Então você também precisa adicionar.

     else this.toString = function () { return this.name + ': ' + this.message } 
  • O IE não considerará UserError como sendo um instanceof Error menos que você execute o seguinte algum tempo antes de throw UserError

     UserError.prototype = Error.prototype 

Para evitar o clichê para cada tipo diferente de erro, combinei a sabedoria de algumas das soluções em uma function createErrorType :

 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; } 

Então você pode definir novos tipos de erros 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'; }); 

Em resumo:

  • Se você estiver usando ES6 sem transpilers :

     class CustomError extends Error { /* ... */} 
  • Se você estiver usando o Babel transpiler :

Opção 1: use o babel-plugin-transform-built-in-extend

Opção 2: faça você mesmo (inspirado na 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 o framework Classtrophobic

Explicação:

Por que estender a class Error usando ES6 e Babel é um problema?

Porque uma instância de 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 

Na verdade, a partir da documentação oficial do Babel, você não pode estender nenhuma class JavaScript interna , como Date , Array , DOM ou Error .

A questão é descrita aqui:

  • O nativo estende as quebras HTMLELement, Array e outras
  • Um object de A class que é extendida por tipo base como Array, Number, Object, String ou Error não é instância dessa class

E quanto às outras respostas do SO?

Todas as respostas dadas corrigem o instanceof , mas você perde o erro regular console.log :

 console.log(new CustomError('test')); // output: // CustomError {name: "MyError", message: "test", stack: "Error↵ at CustomError (:4:19)↵ at :1:5"} 

Considerando que, usando o método mencionado acima, não apenas você corrige o instanceof , mas também mantém o erro regular console.log :

 console.log(new CustomError('test')); // output: // Error: test // at CustomError (:2:32) // at :1:5 

Em 2017 , acho que esse é o melhor caminho; que suporta o IE9 + e navegadores modernos.

 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 } }); } 

__proto__ também em atenção que a propriedade __proto__ está obsoleta, o que é amplamente utilizado em outras respostas.

Nota: Veja a discussão nos comentários e a essência do @ MattBrowne.

UPDATE: Adicionado atribuição de constructor . Veja este teste para comparação em diferentes implementações. Módulo de teste aqui .

Resposta de Crescent Fresh resposta altamente votada é enganosa. Embora seus avisos sejam inválidos, existem 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 codificar “um monte de if (instância de erro de MyError) else …” é de alguma forma onerosa ou detalhada em comparação com várias instruções catch. Múltiplas instâncias de instruções em um único bloco catch são tão concisas quanto várias instruções catch – código limpo e conciso sem nenhum truque. Essa é uma ótima maneira de simular o tratamento de erros específico do subtipo jogável do Java.

WRT “aparece a propriedade de mensagem da subclass não é definida”, não é o caso se você usar uma subclass Error construída adequadamente. Para criar sua própria subclass ErrorX Error, apenas copie o bloco de código que começa com “var MyError =”, alterando a palavra “MyError” para “ErrorX”. (Se você quiser adicionar methods personalizados à sua subclass, siga o texto da amostra).

A limitação real e significativa da subclass de erros JavaScript é que, para implementações ou depuradores de JavaScript que rastreiam e relatam rastreamento de pilha e local de instanciação, como o FireFox, um local em sua própria implementação da subclass Error será registrado como o ponto de instanciação do class, enquanto que se você usou um erro direto, seria o local onde você correu “novo erro (…)”). Os usuários do IE provavelmente nunca perceberiam, mas os usuários do Fire Bug no FF verão valores inúteis do nome do arquivo e do número de linha informados junto com esses Erros, e terão que detalhar o rastreamento da pilha para o elemento # 1 para encontrar o local de instanciação real.

Por uma questão de completude – só porque nenhuma das respostas anteriores mencionou este método – se você está trabalhando com o Node.js e não precisa se preocupar com a compatibilidade do navegador, o efeito desejado é muito fácil de ser alcançado com o construído em inherits do módulo util ( documentos oficiais aqui ).

Por exemplo, vamos supor que você queira criar uma class de erro personalizada que usa 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 de pilha terá o nome e a linha corretos, e a instância de erro terá o nome correto!

Isso acontece devido ao uso do método captureStackTrace , que cria uma propriedade stack no object de destino (nesse caso, o CustomError sendo instanciado). Para mais detalhes sobre como funciona, verifique a documentação aqui .

Como sobre esta solução?

Em vez de lançar seu erro personalizado usando:

 throw new MyError("Oops!"); 

Você envolveria o object Error (como um Decorator):

 throw new MyError(Error("Oops!")); 

Isso garante que todos os atributos estejam corretos, como a pilha, fileName lineNumber, et cetera.

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(); }; 

Minha solução é mais simples do que as outras respostas fornecidas e não tem as desvantagens.

Ele preserva a cadeia de protótipos Erro e todas as propriedades em Erro sem precisar de conhecimento específico sobre elas. Ele foi testado no Chrome, Firefox, Node e IE11.

A única limitação é uma input extra no topo da pilha de chamadas. Mas isso é facilmente ignorado.

Veja 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; }; 

No exemplo acima, Error.apply (também Error.call ) não faz nada para mim (Firefox 3.6 / Chrome 5). Uma solução 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'; 

Como algumas pessoas disseram, é relativamente fácil com o ES6:

 class CustomError extends Error { } 

Então eu tentei isso dentro do meu aplicativo, (Angular, Typescript) e simplesmente não funcionou. Depois de algum tempo, descobri que o problema está vindo do Datilografe: O

Veja 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 Typescript no seu projeto no playground Typescript, 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; } } 

Eu só quero adicionar ao que os outros já afirmaram:

Para garantir que a class de erro personalizada seja exibida corretamente no rastreamento de pilha, você precisa definir a propriedade de nome do protótipo da class de erro personalizada como a propriedade de nome da class de erro personalizada. É isso que eu quero dizer:

 CustomError.prototype = Error.prototype; CustomError.prototype.name = 'CustomError'; 

Então 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 dito e feito, você lança sua nova exceção e parece que isso (eu preguiçosamente tentei isso nas ferramentas de desenvolvimento do Chrome):

 CustomError: Stuff Happened. GASP! at Error.CustomError (:3:19) at :2:7 at Object.InjectedScript._evaluateOn (:603:39) at Object.InjectedScript._evaluateAndWrap (:562:52) at Object.InjectedScript.evaluate (:481:21) 

Meus 2 centavos:

Por que outra resposta?

a) Porque acessar a propriedade Error.stack (como em algumas respostas) tem uma grande penalidade de desempenho.

b) Porque é apenas uma linha.

c) Porque a solução em https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error não parece 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/

O que isso faz?

this.__proto__.__proto__ é MyError.prototype.__proto__ , por isso está definindo o __proto__ FOR ALL INSTANCES de MyError para um erro específico recém-criado. Ele mantém propriedades e methods da class MyError e também coloca as novas propriedades Error (incluindo .stack) na cadeia __proto__ .

Problema óbvio:

Você não pode ter mais de uma instância de 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__= does.

Como exceções de JavaScript são difíceis de subclasss, não subclasss. Acabei de criar uma nova class de exceção e usar um erro dentro dela. Eu mudo 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á como esperado, por exemplo:

 throw new InvalidInputError("Input must be a string"); // Output: Uncaught InvalidInputError: Input must be a string 

Ressalva: o rastreamento de pilha não é perfeito, pois levará você para onde o novo Erro é criado e não para onde você joga. Isso não é um grande problema no Chrome, pois fornece um rastreamento completo de pilha diretamente no console. Mas é mais problemático no Firefox, por exemplo.

A maneira de fazer isso corretamente é retornar o resultado da aplicação do construtor, assim como configurar o protótipo da maneira complicada usual do javascripty:

 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 //  

Os únicos problemas com esta maneira de fazer isso neste momento (eu iterei um pouco) são

  • propriedades diferentes de stack e message não são incluídas no MyError e
  • o stacktrace tem uma linha adicional que não é realmente necessária.

O primeiro problema poderia ser corrigido através da iteração de todas as propriedades não enumeráveis ​​do erro, usando o truque desta resposta: É possível obter os nomes de propriedade herdados não enumeráveis ​​de um object? , mas isso não é suportado por ou seja, <9. O segundo problema poderia ser resolvido com a retirada dessa linha no rastreamento de pilha, mas não tenho certeza de como fazer isso com segurança (talvez apenas removendo a segunda linha de e.stack.toString () ??).

Como apontado na resposta de Mohsen, no ES6 é possível estender erros usando classs. É muito mais fácil e seu comportamento é mais consistente com erros nativos … mas infelizmente não é simples usar isso no navegador se você precisar oferecer suporte a navegadores pré-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 como:

 class CustomError extends Error {} 

… e você pode detectar suporte para classs ES6 com try {eval('class X{}') , mas você receberá um erro de syntax se tentar include a versão ES6 em um script carregado por navegadores mais antigos. Assim, a única maneira de suportar todos os navegadores seria carregar um script separado dinamicamente (por exemplo, via AJAX ou eval() ) para navegadores que suportam o ES6. Uma complicação adicional é que eval() não é suportado em 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, a primeira abordagem acima ou simplesmente usar o Error diretamente, sem tentar estendê-lo, parece ser o melhor que praticamente pode ser feito para o código que precisa suportar navegadores não-ES6.

Há uma outra abordagem que algumas pessoas podem querer considerar, que é usar Object.setPrototypeOf() onde Object.setPrototypeOf() disponível para criar um object de erro que seja uma instância do tipo de erro personalizado, mas que se pareça mais com um erro nativo no console (graças à resposta de Ben para a recomendação). Aqui está minha opinião sobre essa abordagem: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Mas dado que um dia poderemos usar o ES6, pessoalmente, não tenho certeza se a complexidade dessa abordagem vale a pena.

Eu iria dar um passo para trás e considerar por que você quer fazer isso? Eu acho que o ponto é lidar com erros diferentes de maneira diferente.

Por exemplo, no Python, você pode restringir a instrução catch para capturar apenas MyValidationError , e talvez você queira fazer algo semelhante em javascript.

 catch (MyValidationError e) { .... } 

Você não pode fazer isso em javascript. Só vai haver um bloco de captura. Você deveria usar uma declaração if no erro para determinar seu tipo.

catch(e) { if(isMyValidationError(e)) { ... } else { // maybe rethrow? throw e; } }

Acho que, em vez disso, lançaria um object bruto com um tipo, uma mensagem e qualquer outra propriedade que você considerasse adequada.

 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 } 

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 em JavaScript. A ideia é estender o erro personalizado de Bailey com um decorador que o envolva, permitindo que você crie novos erros personalizados facilmente.

Nota: Isso só funcionará no V8. There is no support for Error.captureStackTrace in other environments.

Definir

The decorator takes a name for the error type, and returns a function that takes an error message, and encloses the error name.

 CoreError = (@message) -> @constructor.prototype.__proto__ = Error.prototype Error.captureStackTrace @, @constructor @name = @constructor.name BaseError = (type) -> (message) -> new CoreError "#{ type }Error: #{ message }" 

Usar

Now it is simple to create new error types.

 StorageError = BaseError "Storage" SignatureError = BaseError "Signature" 

For fun, you could now define a function that throws a SignatureError if it is called with too many args.

 f = -> throw SignatureError "too many args" if arguments.length 

This has been tested pretty well and seems to work perfectly on V8, maintaing the traceback, position etc.

Note: Using new is optional when constructing a custom error.