Quelle est la bonne façon d'étendre l'erreur en JavaScript?

369

Je veux jeter certaines choses dans mon code JS et je veux qu'elles soient instanceof Error, mais je veux aussi qu'elles soient autre chose.

En Python, généralement, on sous-classe Exception.

Quelle est la chose appropriée à faire dans JS?

Josh Gibson
la source

Réponses:

218

Le seul objet d'erreur de champ standard est la messagepropriété. (Voir MDN ou Spécification du langage EcmaScript, section 15.11) Tout le reste est spécifique à la plate-forme.

Les environnements de mâts définissent la stackpropriété, mais fileNameetlineNumber sont pratiquement inutiles à utiliser dans l' héritage.

Ainsi, l'approche minimaliste est:

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

Vous pouvez renifler la pile, en décaler les éléments indésirables et en extraire des informations telles que fileName et lineNumber, mais cela nécessite des informations sur la plateforme sur laquelle JavaScript est actuellement exécuté. La plupart des cas, ce n'est pas nécessaire - et vous pouvez le faire en post-mortem si vous le voulez vraiment.

Safari est une exception notable. Il n'y a pas de stackpropriété, mais le throwmot clé définit sourceURLetline propriétés de l'objet qui est lancé. Ces choses sont garanties d'être correctes.

Les cas de test que j'ai utilisés peuvent être trouvés ici: JavaScript self-made Error comparaison d'objets .

Tero
la source
19
Vous pouvez déplacer l' this.name = 'MyError' extérieur de la fonction et la modifier MyError.prototype.name = 'MyError'.
Daniel Beardsley
36
C'est la seule bonne réponse ici, bien que pour une question de style, je l'écrirais probablement comme ceci. function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
kybernetikos
11
J'ajouterais MyError.prototype.constructor = MyErroraussi.
Bharat Khatri
3
dans ES6 Error.call (this, message); devrait initialiser this, non?
4esn0k
4
MyError.prototype = Object.create (Error.prototype);
Robin comme l'oiseau
170

Dans ES6:

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

la source

Mohsen
la source
53
Il convient de mentionner que cela ne fonctionne pas si vous utilisez des fonctionnalités ES6 via un transpilateur, tel que Babel, car les sous-classes doivent étendre une classe.
aaaidan
6
Si vous utilisez babel et que vous êtes sur un nœud> 5.x, vous ne devriez pas utiliser le préréglage es2015, mais npmjs.com/package/babel-preset-node5 vous permettrait d'utiliser des extensions natives es6 et plus encore
Ace
2
C'est la meilleure façon de le faire lorsque cela est possible. Les erreurs personnalisées se comportent plus comme des erreurs normales dans Chrome et Firefox (et probablement aussi dans d'autres navigateurs).
Matt Browne
2
Pour les navigateurs, notez que vous pouvez détecter la prise en charge des classes au moment de l'exécution et revenir en conséquence à une version non-classe. Code de détection:var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
Matt Browne
31
Pour faciliter l'entretien , utilisez this.name = this.constructor.name;plutôt.
Константин Ван
53

En bref:

  • Si vous utilisez ES6 sans transpilers :

    class CustomError extends Error { /* ... */}
  • Si vous utilisez le transpilateur Babel :

Option 1: utilisez babel-plugin-transform-builtin-extend

Option 2: faites-le vous-même (inspiré de cette même bibliothèque)

    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);
  • Si vous utilisez pure ES5 :

    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;
    }
  • Alternative: utiliser un cadre classtrophobe

Explication:

Pourquoi étendre la classe Error en utilisant ES6 et Babel est un problème?

Parce qu'une instance de CustomError n'est plus reconnue comme telle.

class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false

En fait, de la documentation officielle de Babel, vous ne pouvez pas étendre tout JavaScript intégré dans les classes telles que Date, Array, DOMou Error.

Le problème est décrit ici:

Qu'en est-il des autres réponses SO?

Toutes les réponses données corrigent le instanceofproblème mais vous perdez l'erreur régulière console.log:

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

Alors qu'en utilisant la méthode mentionnée ci-dessus, non seulement vous corrigez le instanceofproblème, mais vous conservez également l'erreur régulière console.log:

console.log(new CustomError('test'));
// output:
// Error: test
//     at CustomError (<anonymous>:2:32)
//     at <anonymous>:1:5
JBE
la source
1
class CustomError extends Error { /* ... */}ne gère pas correctement les arguments spécifiques au fournisseur ( lineNumber, etc.), «Extension d'erreur en Javascript avec la syntaxe ES6» est spécifique à Babel, votre solution ES5 utilise constet ne gère pas les arguments personnalisés.
Indolering
Réponse très complète.
Daniele Orlando
Cela fournit en fait la solution la plus complète et explique pourquoi les différentes pièces sont nécessaires. Merci beaucoup JBE!
AndrewSouthpaw
Cela m'a aidé à résoudre le problème d'héritage de "Error". Ce fut un cauchemar de deux heures!
Iulian Pinzaru
2
Il convient de noter que le problème avec console.log(new CustomError('test') instanceof CustomError);// falseétait vrai au moment de la rédaction du présent rapport, mais qu'il a maintenant été résolu. En fait, le problème lié dans la réponse a été résolu et nous pouvons tester le comportement correct ici et en collant le code dans le REPL et en voyant comment il est correctement transpilé pour instancier avec la chaîne de prototype correcte.
Nobita
44

Edit: Veuillez lire les commentaires. Il s'avère que cela ne fonctionne que bien dans V8 (Chrome / Node.JS) Mon intention était de fournir une solution multi-navigateur, qui fonctionnerait dans tous les navigateurs, et fournirait une trace de pile là où le support est là.

Modifier: j'ai créé ce wiki communautaire pour permettre plus de modifications.

La solution pour V8 (Chrome / Node.JS), fonctionne dans Firefox et peut être modifiée pour fonctionner principalement correctement dans IE. (voir fin du 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
}

Message original sur "Montrez-moi le code!"

Version courte:

function UserError(message) {
  this.constructor.prototype.__proto__ = Error.prototype
  Error.captureStackTrace(this, this.constructor)
  this.name = this.constructor.name
  this.message = message
}

Je garde this.constructor.prototype.__proto__ = Error.prototypeà l'intérieur de la fonction pour garder tout le code ensemble. Mais vous pouvez également remplacer this.constructorpar UserErroret cela vous permet de déplacer le code vers l'extérieur de la fonction, de sorte qu'il n'est appelé qu'une seule fois.

Si vous suivez cette route, assurez-vous d'appeler cette ligne avant la première fois que vous lancez UserError.

Cette mise en garde n'applique pas la fonction, car les fonctions sont créées en premier, quel que soit l'ordre. Ainsi, vous pouvez déplacer la fonction à la fin du fichier, sans problème.

Compatibilité du navigateur

Fonctionne dans Firefox et Chrome (et Node.JS) et remplit toutes les promesses.

Internet Explorer échoue dans les cas suivants

  • Les erreurs ne doivent err.stackpas commencer, donc "ce n'est pas ma faute".

  • Error.captureStackTrace(this, this.constructor) n'existe pas, vous devez donc faire autre chose comme

    if(Error.captureStackTrace) // AKA if not IE
        Error.captureStackTrace(this, this.constructor)
  • toStringcesse d'exister lorsque vous sous-classe Error. Vous devez donc également ajouter.

    else
        this.toString = function () { return this.name + ': ' + this.message }
  • IE ne sera pas considéré UserErrorcomme un instanceof Errorsauf si vous exécutez la commande suivante un certain temps avantthrow UserError

    UserError.prototype = Error.prototype
George Bailey
la source
16
Je ne pense pas que Firefox possède réellement captureStackTrace. C'est une extension V8 et n'est pas définie dans Firefox pour moi, et je ne trouve aucune référence sur le Web à Firefox qui la supporte. (Merci quand même!)
Geoff
5
Error.call(this)ne fait en effet rien car il retourne une erreur plutôt que de la modifier this.
kybernetikos
1
Fonctionne très bien pour Node.js
Rudolf Meijering
1
UserError.prototype = Error.prototypeest trompeur. Cela ne fait pas d'héritage, cela fait d'eux la même classe .
Halcyon
1
Je pense que Object.setPrototypeOf(this.constructor.prototype, Error.prototype)c'est préférable this.constructor.prototype.__proto__ = Error.prototype, du moins pour les navigateurs actuels.
ChrisV
29

Pour éviter le passe- partout pour chaque type d'erreur, j'ai combiné la sagesse de certaines des solutions en une  createErrorTypefonction:

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

Vous pouvez ensuite définir facilement de nouveaux types d'erreur comme suit:

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';
});
Ruben Verborgh
la source
Y a-t-il une raison pour laquelle vous avez toujours besoin de la ligne this.name = name;?
Peter Tseng
@PeterTseng Puisque namec'est déjà installé sur le prototype, ce n'est plus nécessaire. Je l'ai retiré. Merci!
Ruben Verborgh
27

En 2018 , je pense que c'est la meilleure façon; qui prend en charge IE9 + et les navigateurs modernes.

MISE À JOUR : Voir ce test et dépôt pour une comparaison sur différentes implémentations.

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

Méfiez-vous également que la __proto__propriété est obsolète, ce qui est largement utilisé dans d'autres réponses.

Onur Yıldırım
la source
1
Pourquoi utilisez-vous setPrototypeOf()? Au moins selon MDN, il est généralement déconseillé de l'utiliser si vous pouvez accomplir la même chose en définissant simplement la .prototypepropriété sur le constructeur (comme vous le faites dans le elsebloc pour les navigations qui n'en ont pas setPrototypeOf).
Matt Browne
Changer le prototype d'un objet est déconseillé tous ensemble, non setPrototypeOf. Mais si vous en avez toujours besoin (comme le demande OP), vous devez utiliser la méthodologie intégrée. Comme MDN l'indique, cela est considéré comme la bonne façon de définir le prototype d'un objet. En d'autres termes, MDN dit de ne pas modifier le prototype (car cela affecte les performances et l'optimisation) mais si vous devez, utilisez setPrototypeOf.
Onur Yıldırım
Mon point était que je ne pense pas que vous ayez réellement besoin de changer le prototype ici. Vous pouvez simplement utiliser votre ligne en bas ( CustomError.prototype = Object.create(Error.prototype)). Définit également Object.setPrototypeOf(CustomError, Error.prototype)le prototype du constructeur lui-même plutôt que de spécifier le prototype pour de nouvelles instances de CustomError. Quoi qu'il en soit, en 2016, je pense qu'il existe en fait une meilleure façon d'étendre les erreurs, même si je suis toujours en train de trouver comment l'utiliser avec Babel: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/…
Matt Browne
CustomError.prototype = Object.create(Error.prototype)modifie également le prototype. Vous devez le changer car il n'y a pas de logique d'extension / héritage intégrée dans ES5. Je suis sûr que le plugin babel que vous mentionnez fait des choses similaires.
Onur Yıldırım
1
J'ai créé un résumé montrant pourquoi l'utilisation Object.setPrototypeOfn'a pas de sens ici, du moins pas de la façon dont vous l'utilisez: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Peut-être que vous vouliez écrire Object.setPrototypeOf(CustomError.prototype, Error.prototype)- cela aurait un peu plus de sens (bien qu'il n'apporte toujours aucun avantage par rapport au simple réglage CustomError.prototype).
Matt Browne
19

Par souci d'exhaustivité - simplement parce qu'aucune des réponses précédentes n'a mentionné cette méthode - si vous travaillez avec Node.js et n'avez pas à vous soucier de la compatibilité du navigateur, l'effet souhaité est assez facile à obtenir avec le intégré inheritsdu utilmodule ( documents officiels ici ).

Par exemple, supposons que vous souhaitiez créer une classe d'erreur personnalisée qui prend un code d'erreur comme premier argument et le message d'erreur comme deuxième argument:

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

Vous pouvez maintenant instancier et passer / lancer votre 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!');

Notez qu'avec cet extrait, la trace de la pile aura le nom et la ligne de fichier corrects et l'instance d'erreur aura le nom correct!

Cela se produit en raison de l'utilisation de la captureStackTraceméthode, qui crée une stackpropriété sur l'objet cible (dans ce cas, l' CustomErrorinstanciation). Pour plus de détails sur son fonctionnement, consultez la documentation ici .

Victor Schröder
la source
1
this.message = this.message;est-ce mal ou y a-t-il encore des choses folles que je ne connais pas sur JS?
Alex
1
Hé @Alex, tu as tout à fait raison! C'est réparé maintenant. Merci!
Victor Schröder
18

La réponse votée par Crescent Fresh est trompeuse. Bien que ses avertissements ne soient pas valides, il existe d'autres limitations qu'il ne traite pas.

Premièrement, le raisonnement du paragraphe "Mises en garde:" de Crescent n'a pas de sens. L'explication implique que coder "un tas de if (erreur instanceof MyError) d'autre ..." est en quelque sorte fastidieux ou verbeux par rapport à plusieurs déclarations catch. Les instructions instanceof multiples dans un seul bloc catch sont tout aussi concises que les instructions catch multiples - du code propre et concis sans astuces. C'est un excellent moyen d'émuler la grande gestion des erreurs spécifiques au sous-type jetable de Java.

WRT "apparaît que la propriété de message de la sous-classe n'est pas définie", ce n'est pas le cas si vous utilisez une sous-classe Error correctement construite. Pour créer votre propre sous-classe ErrorX Error, copiez simplement le bloc de code commençant par "var MyError =", en remplaçant le mot "MyError" par "ErrorX". (Si vous souhaitez ajouter des méthodes personnalisées à votre sous-classe, suivez l'exemple de texte).

La limitation réelle et significative de la sous-classification des erreurs JavaScript est que pour les implémentations ou les débogueurs JavaScript qui suivent et signalent la trace de la pile et l'emplacement de l'instanciation, comme FireFox, un emplacement dans votre propre implémentation de la sous-classe Error sera enregistré comme point d'instanciation de la alors que si vous utilisiez une erreur directe, ce serait l'emplacement où vous avez exécuté "nouvelle erreur (...)"). Les utilisateurs d'IE ne le remarqueraient probablement jamais, mais les utilisateurs de Fire Bug sur FF verront les valeurs de nom de fichier et de numéro de ligne inutiles rapportées à côté de ces erreurs, et devront explorer la trace de la pile jusqu'à l'élément # 1 pour trouver l'emplacement réel d'instanciation.

Blaine
la source
Ai-je bien compris - que si vous ne sous-classez pas et n'utilisez pas directement une nouvelle erreur (...), le nom et la ligne du fichier sont correctement signalés? Et vous dites essentiellement que sur la pratique (réelle et pas seulement sexy ou décorative) les erreurs de sous-classement, ça n'a aucun sens?
jayarjo
6
Cette réponse est un peu déroutante car elle Crescent Fresh'sa été supprimée!
Peter
Est-ce toujours le cas? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Le numéro de ligne n'est pas 2 où le nouveau a été appelé
Muhammad Umer
13

Et cette solution?

Au lieu de lancer votre erreur personnalisée en utilisant:

throw new MyError("Oops!");

Vous emballeriez l'objet Error (un peu comme un décorateur):

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

Cela garantit que tous les attributs sont corrects, tels que la pile, fileName lineNumber, et cetera.

Il vous suffit alors de copier les attributs ou de définir des getters pour eux. Voici un exemple d'utilisation de 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();
};
JoWie
la source
1
J'ai publié cette solution sous forme de package npm: npmjs.com/package/throwable
JoWie
Solution incroyablement élégante, merci pour le partage! Une variation: new MyErr (arg1, arg2, new Error())et dans le constructeur MyErr, nous utilisons Object.assignpour assigner les propriétés du dernier arg àthis
Peeyush Kushwaha
J'aime ça. Vous contournez une limitation en utilisant l'encapsulation au lieu de l'héritage.
Jenny O'Reilly
13

Comme certains l'ont dit, c'est assez facile avec ES6:

class CustomError extends Error { }

J'ai donc essayé cela dans mon application (Angular, Typescript) et cela n'a tout simplement pas fonctionné. Après un certain temps, j'ai constaté que le problème venait de Typescript: O

Voir https://github.com/Microsoft/TypeScript/issues/13965

C'est très dérangeant car si vous le faites:

class CustomError extends Error {}


try {
  throw new CustomError()
} catch(e) {
  if (e instanceof CustomError) {
    console.log('Custom error');
  } else {
    console.log('Basic error');
  }
}

Dans le nœud ou directement dans votre navigateur, il affichera: Custom error

Essayez de l'exécuter avec Typescript dans votre projet sur le terrain de jeu Typescript, il s'affichera Basic error ...

La solution consiste à procéder comme suit:

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;
  }
}
maxime1992
la source
10

Ma solution est plus simple que les autres réponses fournies et n'a pas les inconvénients.

Il préserve la chaîne de prototypes Error et toutes les propriétés sur Error sans avoir besoin de connaissances spécifiques à leur sujet. Il a été testé dans Chrome, Firefox, Node et IE11.

La seule limitation est une entrée supplémentaire en haut de la pile d'appels. Mais cela est facilement ignoré.

Voici un exemple avec deux paramètres personnalisés:

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

Exemple d'utilisation:

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
}

Pour les environnements qui nécessitent un polyfil de setPrototypeOf:

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
};
Ben
la source
1
comme cela est documenté dans ma réponse, cette solution peut provoquer un problème dans Firefox ou dans d'autres navigateurs qui enregistrent uniquement la première ligne de la trace de pile dans la console
Jonathan Benn
C'est la seule réponse que j'ai trouvée qui fonctionne bien avec ES5 (l'utilisation des classes ES6 fonctionne bien aussi). Les erreurs s'affichent beaucoup mieux dans Chromium DevTools que les autres réponses.
Ben Gubler
Remarque: si vous utilisez cette solution avec TypeScript, vous devez exécuter throw CustomError('err')au lieu dethrow new CustomError('err')
Ben Gubler
8

Dans l'exemple ci-dessus Error.apply(aussi Error.call) ne fait rien pour moi (Firefox 3.6 / Chrome 5). Une solution de contournement que j'utilise est:

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';
panzi
la source
5

Dans Node, comme d'autres l'ont dit, c'est simple:

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);
}
Muhammad Umer
la source
3

Je veux juste ajouter à ce que d'autres ont déjà déclaré:

Pour vous assurer que la classe d'erreur personnalisée s'affiche correctement dans la trace de la pile, vous devez définir la propriété de nom du prototype de la classe d'erreur personnalisée sur la propriété de nom de la classe d'erreur personnalisée. Voici ce que je veux dire:

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

Ainsi, l'exemple complet serait:

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

Quand tout est dit et fait, vous lancez votre nouvelle exception et cela ressemble à ceci (j'ai paresseusement essayé cela dans les outils de développement de 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)
Gautham C.
la source
5
Cela n'écrase-t-il pas la propriété name pour TOUTES les instances d'erreur?
panzi
@panzi vous avez raison. J'ai corrigé mon petit bug. Merci pour l'information!
Gautham C.
3

Mes 2 cents:

Pourquoi une autre réponse?

a) Parce que l'accès à la Error.stackpropriété (comme dans certaines réponses) a une grande pénalité de performance.

b) Parce que ce n'est qu'une seule ligne.

c) Parce que la solution sur https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error ne semble pas conserver les informations de la pile.

//MyError class constructor
function MyError(msg){
    this.__proto__.__proto__ = Error.apply(null, arguments);
};

exemple d'utilisation

http://jsfiddle.net/luciotato/xXyeB/

Qu'est ce que ça fait?

this.__proto__.__proto__est MyError.prototype.__proto__, il définit donc __proto__FOR ALL INSTANCES of MyError sur une erreur spécifique nouvellement créée. Il conserve les propriétés et méthodes de la classe MyError et place également les nouvelles propriétés Error (y compris .stack) dans la __proto__chaîne.

Problème évident:

Vous ne pouvez pas avoir plus d'une instance de MyError avec des informations utiles sur la pile.

N'utilisez pas cette solution si vous ne comprenez pas bien ce qui se this.__proto__.__proto__=passe.

Lucio M. Tato
la source
2

Étant donné que les exceptions JavaScript sont difficiles à sous-classer, je ne sous-classe pas. Je viens de créer une nouvelle classe d'exception et j'utilise une erreur à l'intérieur de celle-ci. Je modifie la propriété Error.name pour qu'elle ressemble à mon exception personnalisée sur la console:

var InvalidInputError = function(message) {
    var error = new Error(message);
    error.name = 'InvalidInputError';
    return error;
};

La nouvelle exception ci-dessus peut être levée comme une erreur régulière et fonctionnera comme prévu, par exemple:

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

Attention: la trace de la pile n'est pas parfaite, car elle vous amènera à l'endroit où la nouvelle erreur est créée et non à l'endroit où vous jetez. Ce n'est pas un gros problème sur Chrome, car il vous fournit une trace de pile complète directement dans la console. Mais c'est plus problématique sur Firefox, par exemple.

Jonathan Benn
la source
Cela échoue dans le casm = new InvalidInputError(); dontThrowMeYet(m);
Eric
@Eric, je suis d'accord, mais cela semble être une assez petite limitation. Je n'ai jamais eu besoin d'instancier un objet d'exception à l'avance (à l'exception des utilisations de méta-programmation comme mon exemple de code ci-dessus). Est-ce vraiment un problème pour vous?
Jonathan Benn
Oui, le comportement semble être le même, je vais donc changer ma réponse. Je ne suis pas satisfait à 100% de la trace de la pile, ce qui vous amène à la ligne "var error" sur Firefox et Chrome
Jonathan Benn
1
@JonathanBenn Je suis vraiment en retard à la fête, alors peut-être que vous avez déjà compris cela. J'instancie fréquemment un objet d'exception lorsque j'utilise la programmation et les promesses asynchrones. Suivant les noms de @ Eric, j'utilise souvent m = new ...alors Promise.reject(m). Ce n'est pas une nécessité, mais le code est plus facile à lire.
BaldEagle
1
@JonathanBenn: (he he) le 14 octobre, vous sembliez penser que l'instanciation d'un objet d'exception avant de le lancer serait rare. J'ai donné un exemple d'une fois où je le fais. Je ne dirai pas que c'est commun, mais c'est pratique à avoir quand je le veux. Et, mon code est plus lisible car l'instanciation est tout sur une ligne et le rejet tout sur une autre. J'espère que ça le fait!
BaldEagle
2

Comme indiqué dans la réponse de Mohsen, dans ES6, il est possible d'étendre les erreurs en utilisant des classes. C'est beaucoup plus facile et leur comportement est plus cohérent avec les erreurs natives ... mais malheureusement, ce n'est pas une question simple à utiliser dans le navigateur si vous devez prendre en charge les navigateurs pré-ES6. Voir ci-dessous pour quelques notes sur la façon dont cela pourrait être mis en œuvre, mais en attendant, je suggère une approche relativement simple qui incorpore certaines des meilleures suggestions d'autres réponses:

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

Dans ES6, c'est aussi simple que:

class CustomError extends Error {}

... et vous pouvez détecter la prise en charge des classes ES6 avec try {eval('class X{}'), mais vous obtiendrez une erreur de syntaxe si vous essayez d'inclure la version ES6 dans un script chargé par des navigateurs plus anciens. Par conséquent, la seule façon de prendre en charge tous les navigateurs serait de charger dynamiquement un script distinct (par exemple via AJAX ou eval()) pour les navigateurs prenant en charge ES6. Une autre complication est que ce eval()n'est pas pris en charge dans tous les environnements (en raison des politiques de sécurité du contenu), ce qui peut ou non être une considération pour votre projet.

Donc, pour l'instant, soit la première approche ci-dessus, soit simplement en utilisant Errordirectement sans essayer de l'étendre, cela semble être le meilleur qui puisse être fait pour le code qui doit prendre en charge les navigateurs non ES6.

Il existe une autre approche que certaines personnes voudront peut-être envisager, qui consiste à utiliser, le Object.setPrototypeOf()cas échéant, pour créer un objet d'erreur qui est une instance de votre type d'erreur personnalisé, mais qui ressemble et se comporte davantage comme une erreur native dans la console (grâce à la réponse de Ben pour la recommandation). Voici mon point de vue sur cette approche: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Mais étant donné qu'un jour nous pourrons simplement utiliser ES6, personnellement, je ne suis pas sûr que la complexité de cette approche en vaille la peine.

Matt Browne
la source
1

La façon de faire ce droit est de renvoyer le résultat de la demande du constructeur, ainsi que de définir le prototype de la manière habituelle javascript compliquée:

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

Les seuls problèmes avec cette façon de faire à ce stade (je l'ai un peu répété) sont que

  • propriétés autres que stacket messagene sont pas incluses dansMyError et
  • le stacktrace a une ligne supplémentaire qui n'est pas vraiment nécessaire.

Le premier problème pourrait être résolu en parcourant toutes les propriétés d'erreur non énumérables en utilisant l'astuce dans cette réponse: est-il possible d'obtenir les noms de propriété hérités non énumérables d'un objet? , mais ceci n'est pas supporté par ie <9. Le deuxième problème pourrait être résolu en supprimant cette ligne dans la trace de la pile, mais je ne sais pas comment le faire en toute sécurité (peut-être simplement en supprimant la deuxième ligne de e.stack.toString () ??).

BT
la source
J'ai créé un module qui peut étendre la plupart des anciens objets javascript, y compris les erreurs. Son assez mature à ce stade github.com/fresheneesz/proto
BT
1

L'extrait montre tout.

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)
      }
    }
Amarpreet Singh
la source
0

Je prendrais un peu de recul et je me demanderais pourquoi vous voulez faire ça? Je pense qu'il s'agit de traiter différemment différentes erreurs.

Par exemple, en Python, vous pouvez restreindre l'instruction catch à catch uniquement MyValidationError, et peut-être voulez-vous pouvoir faire quelque chose de similaire en javascript.

catch (MyValidationError e) {
    ....
}

Vous ne pouvez pas faire cela en javascript. Il n'y aura qu'un seul bloc de capture. Vous êtes censé utiliser une instruction if sur l'erreur pour déterminer son type.

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

Je pense que je préfère lancer un objet brut avec un type, un message et toutes les autres propriétés que vous jugez appropriées.

throw { type: "validation", message: "Invalid timestamp" }

Et quand vous attrapez l'erreur:

catch(e) {
    if(e.type === "validation") {
         // handle error
    }
    // re-throw, or whatever else
}
hasen
la source
1
Lancer un objet n'est pas une bonne idée. Vous n'avez pas error.stack, outillage standard ne fonctionnera pas avec elle, etc. , etc. Une meilleure façon serait d'ajouter des propriétés à une instance d'erreur, par exemplevar e = new Error(); e.type = "validation"; ...
timruffles
0

Décorateur d'erreur personnalisé

Ceci est basé sur la réponse de George Bailey , mais étend et simplifie l'idée originale. Il est écrit en CoffeeScript, mais est facile à convertir en JavaScript. L'idée est d'étendre l'erreur personnalisée de Bailey avec un décorateur qui l'enveloppe, vous permettant de créer facilement de nouvelles erreurs personnalisées.

Remarque: cela ne fonctionnera qu'en V8. Il n'y a pas de prise Error.captureStackTraceen charge dans d'autres environnements.

Définir

Le décorateur prend un nom pour le type d'erreur, et renvoie une fonction qui prend un message d'erreur et entoure le nom de l'erreur.

CoreError = (@message) ->

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

BaseError = (type) ->

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

Utilisation

Il est désormais simple de créer de nouveaux types d'erreur.

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

Pour le plaisir, vous pouvez maintenant définir une fonction qui lance un SignatureErrorsi elle est appelée avec trop d'arguments.

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

Cela a été assez bien testé et semble fonctionner parfaitement sur V8, en conservant le traçage, la position, etc.

Remarque: L'utilisation newest facultative lors de la construction d'une erreur personnalisée.

Carl Smith
la source
0

si vous ne vous souciez pas des performances pour les erreurs, c'est le plus petit que vous puissiez faire

Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
    const error = new Error(message)
    Object.setPrototypeOf(error, MyError.prototype);
    return error
}

vous pouvez l'utiliser sans nouveau juste MyError (message)

En changeant le prototype après l'appel de l'erreur constructeur, nous n'avons pas à définir la pile d'appels et le message

bormat
la source
0

Mohsen a une excellente réponse ci-dessus dans ES6 qui définit le nom, mais si vous utilisez TypeScript ou si vous vivez dans le futur où, espérons-le, cette proposition de champs de classe publics et privés a dépassé l'étape 3 en tant que proposition et l'a faite dans l'étape 4 dans le cadre d'ECMAScript / JavaScript, alors vous voudrez peut-être savoir que cela est alors un peu plus court. L'étape 3 est où les navigateurs commencent à implémenter des fonctionnalités, donc si votre navigateur le prend en charge, le code ci-dessous pourrait bien fonctionner. (Testé dans le nouveau navigateur Edge v81, il semble fonctionner correctement). Soyez averti bien que cette fonctionnalité soit instable pour le moment et qu'elle doive être utilisée avec prudence et vous devriez toujours vérifier la prise en charge du navigateur sur les fonctionnalités instables. Ce message est principalement destiné aux futurs habitants lorsque les navigateurs pourraient le prendre en charge. Pour vérifier la vérification de l'assistance MDNetPuis-je utiliser . Il est actuellement pris en charge à 66% sur le marché des navigateurs, mais ce n'est pas génial, donc si vous voulez vraiment l'utiliser maintenant et ne voulez pas attendre, utilisez un transpilateur comme Babel ou quelque chose comme TypeScript .

class EOFError extends Error { 
  name="EOFError"
}
throw new EOFError("Oops errored");

Comparez cela à une erreur sans nom qui, une fois lancée, n'enregistrera pas son nom.

class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");

John
la source