Qual é uma boa maneira de estender o Erro em JavaScript?


369

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:


218

O único campo padrão Erro que o objeto possui é a messagepropriedade (Consulte MDN , ou EcmaScript Language Specification, seção 15.11) Todo o resto é específico da plataforma.

Ambientes mosts definir a stackpropriedade, mas fileNamee lineNumbersã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á stackpropriedade, mas os throwconjuntos de palavras - chave sourceURLe as linepropriedades 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 .


19
Você pode mover a parte this.name = 'MyError' externa da função e alterá-la para MyError.prototype.name = 'MyError'.
22411 Daniel

36
Essa é a única resposta correta aqui, embora, por uma questão de estilo, eu provavelmente a escreva assim. function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
Kybernetikos

11
Eu adicionaria MyError.prototype.constructor = MyErrortambém.
Bharat Khatri

3
no ES6 Error.call (esta, mensagem); deve inicializar this, certo?
4esn0k

4
MyError.prototype = Object.create (Error.prototype);
Robin como o pássaro

170

No ES6:

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

fonte


53
Vale ressaltar que isso não funciona se você estiver usando os recursos do ES6 por meio de um transpilador, como o Babel, pois as subclasses devem estender uma classe.
precisa saber é

6
Se você estiver usando babel e estiver no nó> 5.x, não deverá usar a predefinição es2015, mas npmjs.com/package/babel-preset-node5 permitiria o uso de extensões nativas do es6 e muito mais
Ace

2
Essa é a melhor maneira de fazer isso quando possível. Erros personalizados se comportam mais como erros regulares no Chrome e Firefox (e provavelmente em outros navegadores também).
Matt Browne

2
Para navegadores, observe que você pode detectar o suporte para classes em tempo de execução e retornar a uma versão que não seja de classe. Código de detecção:var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
Matt Browne

31
Para facilitar a manutenção , use em seu this.name = this.constructor.name;lugar.
Константин Ван

53

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, DOMou Error.

O problema está descrito aqui:

E as outras respostas do SO?

Todas as respostas fornecidas corrigem o instanceofproblema, 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 instanceofproblema, 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

11
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 conste não lida com argumentos personalizados.
Indolering

Resposta muito completa.
21418 Daniele Orlando

Isso realmente fornece a solução mais abrangente e explica por que as várias peças são necessárias. Muito obrigado JBE!
AndrewSouthpaw

Isso me ajudou a resolver o problema com a herança de "Erro". Foi um pesadelo de duas horas!
Iulian Pinzaru

2
Vale ressaltar que o problema com console.log(new CustomError('test') instanceof CustomError);// falseera 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.
Nobita

44

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.prototypedentro da função para manter todo o código juntos. Mas você também pode substituirthis.constructor por UserErrore 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.stackcomeç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)
  • toStringdeixa 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á UserErrorum, a instanceof Errormenos que você execute o seguinte algum tempo antes dethrow UserError

    UserError.prototype = Error.prototype

16
Eu não acho que o Firefox realmente tenha captureStackTrace. É uma extensão V8 e não está definida no Firefox para mim, nem posso encontrar referências na Web para o Firefox suportá-lo. (Graças embora!)
Geoff

5
Error.call(this)de fato, não está fazendo nada, pois retorna um erro ao invés de modificar this.
Kybernetikos

11
Funciona muito bem para Node.js
Rudolf Meijering

11
UserError.prototype = Error.prototypeé enganoso. Isso não gera herança, isso os torna da mesma classe .
precisa

11
Eu acredito que Object.setPrototypeOf(this.constructor.prototype, Error.prototype)é preferível this.constructor.prototype.__proto__ = Error.prototype, pelo menos para os navegadores atuais.
ChrisV

29

Para evitar o padrão para cada tipo diferente de erro, combinei a sabedoria de algumas das soluções em uma  createErrorTypefunçã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';
});

Existe uma razão para você ainda precisar da linha this.name = name;?
Peter Tseng

@ PeterTseng Como namejá está definido no protótipo, não é mais necessário. Eu removi isso. Obrigado!
Ruben Verborgh 8/16

27

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.


11
Por que você está usando setPrototypeOf()? Pelo menos de acordo com o MDN, geralmente é desencorajado usá-lo se você puder realizar a mesma coisa, apenas configurando a .prototypepropriedade no construtor (como você faz no elsebloco para navegações que não possuem setPrototypeOf).
Matt Browne

Alterar o protótipo de um objeto é desencorajado por completo, não 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.
Onur Yıldırım

Meu argumento é que não acho que você precise alterar o protótipo aqui. Você pode simplesmente usar sua linha na parte inferior ( 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/…
Matt Browne

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.
Onur Yıldırım

11
Criei uma essência demonstrando por que o uso Object.setPrototypeOfnã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).
Matt Browne

19

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 inheritsdo utilmó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 captureStackTracemétodo, que cria uma stackpropriedade no objeto de destino (nesse caso, o CustomErrorinstanciado). Para mais detalhes sobre como funciona, consulte a documentação aqui .


11
this.message = this.message;isso está errado ou ainda existem coisas malucas que eu não sei sobre JS?
Alex

11
Hey @Alex, você está totalmente certo! Está consertado agora. Obrigado!
Victor Schröder

18

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.


Eu entendi direito - que, se você não fizer uma subclasse e usar o novo Erro (...) diretamente, o nome e a linha do arquivo serão relatados corretamente? E você basicamente diz que na prática (real e não apenas um tipo sexy ou decorativo) de subclassificação de Erros, não faz sentido?
jayarjo

6
Esta resposta é um pouco confusa, pois Crescent Fresh'sfoi excluída!
Peter

esse ainda é o caso? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... Linha número é 2 não onde nova era chamado
Muhammad Umer

13

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

11
Lancei esta solução como um pacote npm
JoWie

Solução incrivelmente elegante, obrigado por compartilhar! Uma variação: new MyErr (arg1, arg2, new Error())e no construtor MyErr, usamos Object.assignpara atribuir as propriedades do último arg parathis
Peeyush Kushwaha

Eu gosto disso. Você ignora uma limitação usando encapsulamento em vez de herança.
Jenny O'Reilly

13

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

10

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

11
como está documentado na minha resposta, esta solução pode causar um problema no Firefox ou outros navegadores que registra somente a primeira linha do rastreamento de pilha no console
Jonathan Benn

Esta é a única resposta que eu achei que funciona bem com o ES5 (o uso de classes ES6 também funciona bem). Os erros são exibidos muito mais bem no Chromium DevTools do que em outras respostas.
precisa

Nota: se você estiver usando esta solução com TypeScript, você deve executar em throw CustomError('err')vez dethrow new CustomError('err')
Ben Gubler

8

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

5

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

3

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)

5
Isso não substitui a propriedade name para TODAS as instâncias de erro?
panzi

@panzi você está correto. Corrigi meu pequeno bug. Obrigado pela atenção!
Gautham C.

3

Meus 2 centavos:

Por que outra resposta?

a) Porque o acesso à Error.stackpropriedade (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/

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.


2

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.


Esta falha no casom = new InvalidInputError(); dontThrowMeYet(m);
Eric

@ Eric, eu concordo, mas isso parece ser uma limitação bastante pequena. Eu nunca precisei instanciar um objeto de exceção antes do tempo (exceto os usos de metaprogramação como meu exemplo de código acima). Isso é realmente um problema para você?
Jonathan Benn

Sim, o comportamento parece ser o mesmo, então vou mudar minha resposta. Eu não estou 100% satisfeito com o rastreamento de pilha, que o leva até a linha "var erro" no Firefox e Chrome
Jonathan Benn

11
@ JonathanBenn Estou muito atrasado para a festa, então talvez você já tenha entendido isso. Eu frequentemente instanciamos um objeto de exceção quando uso programação assíncrona e promessas. Seguindo os nomes de @ Eric, eu costumo usar isso m = new ...então Promise.reject(m). Não é necessário, mas o código é mais fácil de ler.
precisa saber é o seguinte

11
@ JonathanBenn: (ele ele) em 14 de outubro, você parecia pensar em instanciar um objeto de exceção antes de lançá-lo, seria raro. Dei um exemplo de uma vez que faço isso. Não vou dizer que é comum, mas é útil ter quando eu quero. E, meu código é mais legível porque a instanciação é toda em uma linha e a rejeição é toda em outra. Espero que sim!
precisa saber é o seguinte

2

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.


1

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

  • propriedades diferentes stacke messagenão estão incluídas MyErrore
  • o stacktrace tem uma linha adicional que não é realmente necessária.

O 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 () ??).


Criei um módulo que pode estender o objeto javascript antigo mais comum, incluindo erros. Sua bastante amadurecer neste momento github.com/fresheneesz/proto
BT

1

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

0

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

11
Jogar um objeto não é uma ótima idéia. Você não tem 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"; ...
timruffles

0

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.captureStackTraceoutros 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.

CoreError = (@message) ->

    @constructor.prototype.__proto__ = Error.prototype
    Error.captureStackTrace @, @constructor
    @name = @constructor.name

BaseError = (type) ->

    (message) -> new CoreError "#{ type }Error: #{ message }"

Usar

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 SignatureErrorse 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.


0

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


0

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.