web-dev-qa-db-fra.com

Erreur d'extension en Javascript avec la syntaxe ES6 & Babel

J'essaie d'étendre Error avec ES6 et Babel. Ça ne marche pas.

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

L'objet Error ne reçoit jamais le bon ensemble de messages.

Essayez à Babel REPL .

Maintenant, j'ai vu quelques solutions sur SO ( par exemple ici ), mais elles semblent toutes très différentes de l'ES6-y. Comment le faire à la manière de Nice, ES6? (Cela fonctionne à Babel)

120
Karel Bílek

Sur la base de la réponse de Karel Bílek, je voudrais apporter un petit changement à la 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);

Ceci imprimera MyError dans la pile et non le générique Error.

Il ajoutera également le message d'erreur à la trace de la pile - manquante dans l'exemple de Karel.

Il utilisera également captureStackTrace s'il est disponible.

Avec Babel 6, vous avez besoin de transform-builtin-extend ( npm ) pour que cela fonctionne.

166
Lee Benson

En combinant cette réponse , cette réponse et ce code , j'ai créé cette petite classe "d'aide" qui semble fonctionner correctement.

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

Essayez dans REPL

38
Karel Bílek

Pour finalement mettre cela au repos. Dans Babel 6, il est explicite que les développeurs ne prend pas en charge s’étendant de intégré. Bien que cette astuce ne le fera pas aide avec des choses comme Map, Set, etc., cela fonctionne pour Error. Ceci est important car l'une des idées de base d'un langage qui peut générer une exception est d'autoriser les erreurs personnalisées. Ceci est d'autant plus important que les promesses deviennent plus utiles puisqu'elles sont conçues pour rejeter une erreur .

La triste vérité, c’est que vous avez toujours besoin de procéder de la même manière dans ES2015.

Exemple à Babel REPL

Motif d'erreur personnalisé

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

D'autre part, il existe un plugin pour Babel 6 permettant cela.

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

Mise à jour: (à partir du 2016-09-29) Après quelques tests, il apparaît que babel.io ne comptabilise pas correctement toutes les assertions (à partir d'une erreur étendue personnalisée). Mais dans Ember.JS, EXText Error fonctionne comme prévu: https://ember-twiddle.com/d88555a6f408174df0a4c8e0fd6b27ce

24
Sukima

Edit: Interrompre les modifications dans TypeScript 2.1

Les extensions intégrées telles que Error, Array et Map risquent de ne plus fonctionner.

A titre de recommandation, vous pouvez ajuster manuellement le prototype immédiatement après tout appel super (...).

Editer Lee Benson réponse originale un peu fonctionne pour moi. Cela ajoute également stack et des méthodes supplémentaires de la classe ExtendableError à l'instance. 

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);
10
Artur Aleksanyan

Avec les dernières modifications apportées à babel 6, je constate que transform-builtin-extend ne fonctionne plus. J'ai fini par utiliser cette approche mixte:

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;

et

import MyError from './MyError';

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

En conséquence, tous ces tests réussissent:

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');
8
Diego Ferri

Citant

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

Il n’est pas nécessaire de faire appel à this.stack = (new Error()).stack; grâce à l’appel super().

Bien que les codes ci-dessus ne puissent pas générer la trace de pile, sauf si this.stack = (new Error()).stack; ou Error.captureStackTrace(this, this.constructor.name); est appelé dans Babel . OMI, c'est peut-être un problème ici.

En réalité, la trace de la pile peut être sortie sous Chrome console et Node.js v4.2.1 avec ces extraits de code.

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

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

Sortie 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
zangw

En plus de @zangw answer, vous pouvez définir vos erreurs comme suit:

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

qui jettera le nom, le message et le stacktrace correct

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
Honza Stepanovsky

J'essaie d'étendre Error avec ES6

Cette syntaxe class MyError extends Error {…} est correcte.

Notez que les transpilers ont toujours des problèmes d'héritage d'objets intégrés. Dans ton cas,

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

semble résoudre le problème.

2
Bergi

Etant donné que la réponse acceptée ne fonctionne plus, vous pouvez toujours utiliser une usine comme alternative ( 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);

2
Melbourne2991

Cela fonctionne pour moi:

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

Comme @sukima le mentionne, vous ne pouvez pas étendre JS natif. On ne peut pas répondre à la question du PO. 

Similaire à la réponse de Melbourne2991 , j'ai plutôt utilisé une usine, mais j'ai suivi la recommandation de MDN concernant les types d'erreur client .

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
Eric H.

Je préfère une syntaxe plus forte que celle décrite ci-dessus. Des méthodes supplémentaires au type d'erreur vous aideront à créer un joli console.log ou quelque chose d'autre.

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

Pour tester ce code, vous pouvez exécuter quelque chose de similaire:

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

    console.warn(message);
}

Les extensions de type CustomError sont les bienvenues. Il est possible d'ajouter des fonctionnalités spécifiques au type étendu ou de remplacer les fonctions existantes. Par exemple.

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
B. Bohdan

Ne pas utiliser Babel, mais en clair ES6, ce qui suit semble bien fonctionner pour moi:

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

Test de 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 ...'

Comme vous pouvez le constater, la pile contient à la fois le nom de l'erreur et le message. Je ne suis pas sûr d'avoir oublié quelque chose, mais toutes les autres réponses semblent trop compliquer les choses.

0
JHH