Estendendo erro em Javascript com sintaxe ES6 e Babel


132

Estou tentando estender o erro com ES6 e Babel. Não está dando certo.

class MyError extends Error {
  constructor(m) {
    super(m);
  }
}

var error = new Error("ll");
var myerror = new MyError("ll");
console.log(error.message) //shows up correctly
console.log(myerror.message) //shows empty string

O objeto Erro nunca recebe o conjunto de mensagens correto.

Tente em Babel REPL .

Agora, já vi algumas soluções em SO ( por exemplo, aqui ), mas todas parecem muito pouco compatíveis com o ES6-y. Como fazer isso de uma maneira agradável, no ES6? (Isso está trabalhando em Babel)


2
Seguir o seu link para Babel REPL parece indicar que ele está funcionando corretamente agora. Presumo que tenha sido um bug em Babel que foi corrigido desde então.
— kybernetikos

Respostas:


188

Com base na resposta de Karel Bílek, eu faria uma pequena alteração no constructor:

class ExtendableError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
    if (typeof Error.captureStackTrace === 'function') {
      Error.captureStackTrace(this, this.constructor);
    } else { 
      this.stack = (new Error(message)).stack; 
    }
  }
}    

// now I can extend

class MyError extends ExtendableError {}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

Isso será impresso MyErrorna pilha, e não no genérico Error.

Ele também adicionará a mensagem de erro ao rastreamento de pilha - que estava ausente no exemplo de Karel.

Também será usado captureStackTracese estiver disponível.

Com o Babel 6, você precisa transformar o built -in- extension ( npm ) para que isso funcione.


1
@MichaelYounkin if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, this.constructor.name) } else { this.stack = (new Error(message)).stack; } . Eu diria que é melhor usar essa função se estiver disponível, pois ela fornece uma pilha de chamadas mais 'nativa' e imprime o nome do objeto de erro. Obviamente, se você estiver usando isso somente no lado do servidor (Nó), também não será um problema.
— Lee Benson

4
@MichaelYounkin Eu não acho que isso mereça um voto negativo. O OP falou sobre a extensão de erros no ES6. Seguindo essa lógica, quase todo o ES6 está ausente em pelo menos um navegador. Minha solução (com a verificação adicional de função) fornece cobertura nativa no navegador mais amplamente usado, fallback em todos os outros e 100% de cobertura no Node.js. Eu concordo que se você errar o nome da classe de forma consistente, this.stack = (new Error(message)).stackvocê recebe isso ... mas, na prática, isso provavelmente não é um grande problema.
— Lee Benson

6
Isso não funciona em Babel 6:new MyError('foo') instanceof MyError === false
— Sukima 8/16/16

5
Esta pré-compilado com Babel como módulo NPM código: extendable-error-class npmjs.com/package/extendable-error-class que é conveniente para evitar uma dependência em Babel-plug-in-transformar-embutida-estender
— brillout

3
this.message = message;é redundante comsuper(message);
— mathieug

39

Combinando esta resposta , esta resposta e este código , criei esta pequena classe "auxiliar", que parece funcionar bem.

class ExtendableError extends Error {
  constructor(message) {
    super();
    this.message = message; 
    this.stack = (new Error()).stack;
    this.name = this.constructor.name;
  }
}    

// now I can extend

class MyError extends ExtendableError {
  constructor(m) {   
    super(m);
  }
}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

Experimente no REPL


1
this.stack = (new Error(message)).stack;- caso contrário, a mensagem está faltando no stacktrace
— Lee Benson

3
Eu suspeito que isso não funcione conforme necessário, porque se você fizer: console.log (myerror instanceof ExtendableError); ainda diz falso ..
— Mauno Vähä

4
mesmo problema, usar instanceof CustomError não funciona, qual é o objetivo se você não puder usar instanceof.
— gre

Pode ser melhorado adicionando o messageconstrutor Error na pilha, para mostrar a mensagem correta no topo da pilha quando lançada:this.stack = (new Error(message)).stack;
— Sebastien

1
myerror.nameagora retorna "Erro". Não sei se isso está relacionado com versões posteriores do babel.See @ Sukima de resposta abaixo
— Eric H.

27

Para finalmente colocar isso para descansar. Em Babel 6 é explícito que os desenvolvedores não suportam que se estende desde construído em. Embora este truque não vai ajudar com coisas como Map, Set, etc. ela não funciona para Error. Isso é importante, pois uma das idéias principais de uma linguagem que pode gerar uma exceção é permitir erros personalizados. Isso é duplamente importante, pois as promessas se tornam mais úteis, pois são projetadas para rejeitar um erro .

A triste verdade é que você ainda precisa fazer isso da maneira antiga no ES2015.

Exemplo no Babel REPL

Padrão de erro personalizado

class MyError {
  constructor(message) {
    this.name = 'MyError';
    this.message = message;
    this.stack = new Error().stack; // Optional
  }
}
MyError.prototype = Object.create(Error.prototype);

Por outro lado, existe um plugin para o Babel 6 para permitir isso.

https://www.npmjs.com/package/babel-plugin-transform-builtin-extend

Atualização: (a partir de 29/09/2016) Após alguns testes, parece que o babel.io não responde adequadamente por todas as declarações (estendendo-se de um erro estendido personalizado). Mas no Ember.JS, a extensão de Erro funciona conforme o esperado: https://ember-twiddle.com/d88555a6f408174df0a4c8e0fd6b27ce


sim, com o plug-in babel vinculado, ele funciona corretamente com a resposta aceita. (No entanto, o arquivo resultante não funciona no Node, porque não têm Refletir, aparentemente)
— Karel Bílek

Apenas por curiosidade, se as especificações do ES2016 dizem que os componentes internos são extensíveis, por que vms como v8 e es5 de Babel estão tão transpilando? Não é uma expectativa razoável que uma classe possa estender uma classe da mesma maneira que uma cadeia de protótipos pode vir de outros protótipos? Por que a necessidade de tal cerâmica escondida em um plugin?
— Sukima 8/03/16

Isso é particularmente frustrante quando a maioria dos casos de uso quer apenas criar objetos simples que compartilhem comportamentos. Um erro personalizado que pode ser usado Error.toString(). A necessidade de fazer aros e giros especiais para conseguir isso significa que a maioria dos desenvolvedores o evitará e recorrerá a práticas ruins, como jogar cordas em vez de Erros. Ou criando seu próprio mapa como objetos. Por que a necessidade de impedir esses métodos de POO?
— Sukima 8/03/16

Na minha opinião, eles não são contra, é apenas uma questão técnica. Não tenho certeza! Você pode perguntar-lhes :) os projetos são bastante aberto
— Karel Bílek

Até o momento, todas as respostas de perguntas semelhantes sobre o assunto são deixadas em "babel não suporta isso", imaginei que fosse o fim da conversa. Minha carne é que a falta de apoio dificulta um idioma comum de POO e eu até tive que lutar com colegas de trabalho para superá-los. Eu só queria que aqui houvesse uma alternativa alternativa limpa. Parece que adicionar um plugin é a melhor escolha.
— Sukima 8/03/16

15

Edit : Quebrando alterações no TypeScript 2.1

A extensão de incorporados, como Erro, Matriz e Mapa, pode não funcionar mais.

Como recomendação, você pode ajustar manualmente o protótipo imediatamente após qualquer super (...) chamada.

Editar a resposta original de Lee Benson funciona um pouco para mim. Isso também adiciona stackmétodos adicionais de ExtendableErrorclasse à instância.

class ExtendableError extends Error {
   constructor(message) {
       super(message);
       Object.setPrototypeOf(this, ExtendableError.prototype);
       this.name = this.constructor.name;
   }
   
   dump() {
       return { message: this.message, stack: this.stack }
   }
 }    

class MyError extends ExtendableError {
    constructor(message) {
        super(message);
        Object.setPrototypeOf(this, MyError.prototype);
    }
}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror.dump());
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

1
Você precisa chamar Object.setPrototypeOfo MyErrorconstrutor também. stackoverflow.com/a/41102306/186334 github.com/Microsoft/TypeScript-wiki/blob/master/…
— CallMeLaNN

10

Com as alterações mais recentes no babel 6, acho que o transform-builtin-extend não está mais funcionando. Acabei usando essa abordagem mista:

export default class MyError {
    constructor (message) {
        this.name = this.constructor.name;
        this.message = message;
        this.stack = (new Error(message)).stack;
    }
}

MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;

e

import MyError from './MyError';

export default class MyChildError extends MyError {
    constructor (message) {
        super(message);
    }
}

Como resultado, todos esses testes passam:

const sut = new MyError('error message');
expect(sut.message).toBe('error message');
expect(sut).toBeInstanceOf(Error);
expect(sut).toBeInstanceOf(MyError);
expect(sut.name).toBe('MyError');
expect(typeof sut.stack).toBe('string');

const sut = new MyChildError('error message');
expect(sut.message).toBe('error message');
expect(sut).toBeInstanceOf(Error);
expect(sut).toBeInstanceOf(MyError);
expect(sut).toBeInstanceOf(MyChildError);
expect(sut.name).toBe('MyChildError');
expect(typeof sut.stack).toBe('string');

6

Citação

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

Não há necessidade de this.stack = (new Error()).stack;truques graças à super()ligação.

Embora os códigos acima não podem produzir o rastreio da pilha, a menos que this.stack = (new Error()).stack;ou Error.captureStackTrace(this, this.constructor.name);é invocado em Babel . OMI, talvez um problema aqui.

Na verdade, o rastreamento de pilha pode ser gerado sob Chrome consolee Node.js v4.2.1com esses trechos de código.

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

var myerr = new MyError("test");
console.log(myerr.stack);
console.log(myerr);

Saída de Chrome console.

MyError: test
    at MyError (<anonymous>:3:28)
    at <anonymous>:12:19
    at Object.InjectedScript._evaluateOn (<anonymous>:875:140)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34)
    at Object.InjectedScript.evaluate (<anonymous>:664:21)

Saída de Node.js

MyError: test
    at MyError (/home/bsadmin/test/test.js:5:8)
    at Object.<anonymous> (/home/bsadmin/test/test.js:11:13)
    at Module._compile (module.js:435:26)
    at Object.Module._extensions..js (module.js:442:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:311:12)
    at Function.Module.runMain (module.js:467:10)
    at startup (node.js:134:18)
    at node.js:961:3

4

Além da resposta @zangw, você pode definir seus erros assim:

'use strict';

class UserError extends Error {
  constructor(msg) {
    super(msg);
    this.name = this.constructor.name;
  }
}

// define errors
class MyError extends UserError {}
class MyOtherError extends UserError {}

console.log(new MyError instanceof Error); // true

throw new MyError('My message');

que lança nome, mensagem e rastreamento de pilha corretos:

MyError: My message
    at UserError (/Users/honzicek/Projects/api/temp.js:5:10)
    at MyError (/Users/honzicek/Projects/api/temp.js:10:1)
    at Object.<anonymous> (/Users/honzicek/Projects/api/temp.js:14:7)
    at Module._compile (module.js:434:26)
    at Object.Module._extensions..js (module.js:452:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:475:10)
    at startup (node.js:117:18)
    at node.js:951:3

4
Isso não funciona: new MyError('foo') instanceof MyError === false.
— Sukima 8/03/16

1
Faz sim Node.js v7.7.3.
— Gunar Gessner 24/03

2

Estou tentando estender o erro com o ES6

Essa class MyError extends Error {…}sintaxe está correta.

Observe que os transpilers ainda têm problemas com a herança de objetos internos. No seu caso,

var err = super(m);
Object.assign(this, err);

parece corrigir o problema.


Verdade! Mas a mensagem ainda não está definida - vou escrever um novo exemplo.
— Karel Bílek

Eu reescrevi o exemplo agora #
— Karel Bílek


O "super (m)" retornará um objeto vazio, aparentemente. Portanto, Object.assign não ajuda.
— Karel Bílek

@ KarelBílek: Qual navegador você está usando? Error.call()retorna uma nova instância de erro para mim.
— Bergi 27/06/2015

2

Dado isso, a resposta aceita não funciona mais, você sempre pode usar uma fábrica como alternativa ( repl ):

function ErrorFactory(name) {
   return class AppError extends Error {
    constructor(message) {
      super(message);
      this.name = name;
      this.message = message; 
      if (typeof Error.captureStackTrace === 'function') {
        Error.captureStackTrace(this, this.constructor);
      } else { 
        this.stack = (new Error(message)).stack; 
      }
    }
  }     
}

// now I can extend
const MyError = ErrorFactory("MyError");


var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);


A resposta aceita ainda funcionará para mim, se você tiver os plugins babel necessários. Obrigado por esta resposta também!
— Karel Bílek 10/10

2

Eu prefiro uma sintaxe mais forte do que a descrita acima. Métodos adicionais no tipo de erro ajudarão você a criar bonito console.logou algo mais.

export class CustomError extends Error {
    /**
     * @param {string} message
     * @param {number} [code = 0]
     */
    constructor(message, code = 0) {
        super();

        /**
         * @type {string}
         * @readonly
         */
        this.message = message;

        /**
         * @type {number}
         * @readonly
         */
        this.code = code;

        /**
         * @type {string}
         * @readonly
         */
        this.name = this.constructor.name;

        /**
         * @type {string}
         * @readonly
         */
        this.stack = CustomError.createStack(this);
    }

    /**
     * @return {string}
     */
    toString() {
        return this.getPrettyMessage();
    }

    /**
     * @return {string}
     */
    getPrettyMessage() {
        return `${this.message} Code: ${this.code}.`;
    }

    /**
     * @param {CustomError} error
     * @return {string}
     * @private
     */
    static createStack(error) {
        return typeof Error.captureStackTrace === 'function'
            ? Error.captureStackTrace(error, error.constructor)
            : (new Error()).stack;
    }
}

Para testar esse código, você pode executar algo semelhante:

try {
    throw new CustomError('Custom error was thrown!');
} catch (e) {
    const message = e.getPrettyMessage();

    console.warn(message);
}

A extensão do CustomErrortipo é bem-vinda. É possível adicionar alguma funcionalidade específica ao tipo estendido ou substituir existente. Por exemplo.

export class RequestError extends CustomError {
    /**
     * @param {string} message
     * @param {string} requestUrl
     * @param {number} [code = 0]
     */
    constructor(message, requestUrl, code = 0) {
        super(message, code);

        /**
         * @type {string}
         * @readonly
         */
        this.requestUrl = requestUrl;
    }

    /**
     * @return {string}
     */
    getPrettyMessage() {
        const base = super.getPrettyMessage();

        return `${base} Request URL: ${this.requestUrl}.`;
    }
}

1

Como o @sukima menciona, você não pode estender o JS nativo. A pergunta do OP não pode ser respondida.

Semelhante à resposta de Melbourne2991 , usei uma fábrica, mas segui a recomendação da MDN para os tipos de erro do cliente .

function extendError(className){
  function CustomError(message){
    this.name = className;
    this.message = message;
    this.stack = new Error().stack; // Optional
  }
  CustomError.prototype = Object.create(Error.prototype);
  CustomError.prototype.constructor = CustomError;
  return CustomError;
}

1

Isso funciona para mim:

/**
 * @class AuthorizationError
 * @extends {Error}
 */
export class AuthorizationError extends Error {
    message = 'UNAUTHORIZED';
    name = 'AuthorizationError';
}

0

Não usando Babel, mas no ES6 simples, o seguinte parece funcionar bem para mim:

class CustomError extends Error {
    constructor(...args) {
        super(...args);
        this.name = this.constructor.name;
    }
}

Teste do REPL:

> const ce = new CustomError('foobar');
> ce.name
'CustomError'
> ce.message
'foobar'
> ce instanceof CustomError
true
> ce.stack
'CustomError: foobar\n    at CustomError (repl:3:1)\n ...'

Como você pode ver, a pilha contém o nome do erro e a mensagem. Não tenho certeza se estou perdendo alguma coisa, mas todas as outras respostas parecem complicar demais as coisas.


0

Melhorei um pouco a solução do @Lee Benson desta maneira:

extendableError.js

class ExtendableError extends Error {
    constructor(message, errorCode) {
        super(message);
        this.name = this.constructor.name;
        this.errorCode = errorCode
        if (typeof Error.captureStackTrace === 'function') {
            Error.captureStackTrace(this, this.constructor);
        } else {
            this.stack = (new Error(message)).stack;
        }
    }


}

export default ExtendableError

um exemplo de erro

import ExtendableError from './ExtendableError'

const AuthorizationErrors = {
    NOT_AUTHORIZED: 401,
    BAD_PROFILE_TYPE: 402,
    ROLE_NOT_ATTRIBUTED: 403
}

class AuthorizationError extends ExtendableError {
    static errors = AuthorizationErrors 
}

export default AuthorizationError 

Em seguida, você pode agrupar erros enquanto possui especificadores de opção para decidir o que fazer de maneira diferente em algumas situações específicas do aplicativo

new AuthorizationError ("The user must be a seller to be able to do a discount", AuthorizationError.errors.BAD_PROFILE_TYPE )
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.