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)

Karel Bílek
fonte
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.

Lee Benson
fonte
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

Karel Bílek
fonte
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

Sukima
fonte
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);
Artur Aleksanyan
fonte
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');
Diego Ferri
fonte
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
zangw
fonte
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
Honza Stepanovsky
fonte
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.

Bergi
fonte
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
Não funciona para mim
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);

Melbourne2991
fonte
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}.`;
    }
}
B. Bohdan
fonte
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;
}
Eric H.
fonte
1

Isso funciona para mim:

/**
 * @class AuthorizationError
 * @extends {Error}
 */
export class AuthorizationError extends Error {
    message = 'UNAUTHORIZED';
    name = 'AuthorizationError';
}
Michael Liquori
fonte
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.

JHH
fonte
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 )
Zied Hamdi
fonte