503 votes

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

Je veux lancer 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, typiquement, on sous-classe les exceptions.

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

251voto

Tero Points 489

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

La plupart des environnements définissent le stack propriété, mais fileName y lineNumber sont pratiquement inutiles pour être utilisés en héritage.

Donc, 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 pourriez renifler la pile, en retirer les éléments indésirables et extraire des informations telles que le nom du fichier et le numéro de ligne, mais cela nécessite des informations sur la plate-forme sur laquelle JavaScript est exécuté. Dans la plupart des cas, cela n'est pas nécessaire - et vous pouvez le faire en post-mortem si vous le souhaitez vraiment.

Safari est une exception notable. Il n'y a pas de stack mais la propriété throw jeux de mots-clés sourceURL y line de l'objet qui est lancé. Ces éléments sont garantis comme étant corrects.

Les cas de test que j'ai utilisés peuvent être trouvés ici : Comparaison d'objets d'erreur en JavaScript .

20 votes

Vous pourriez déplacer le this.name = 'MyError' en dehors de la fonction et le changer en MyError.prototype.name = 'MyError' .

38 votes

C'est la seule réponse correcte ici, bien que, pour des raisons 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";

0 votes

Exemple de stack-sniffing pour Mozilla : groups.google.com/d/topic/mozilla.dev.tech.js-engine/

48voto

George Bailey Points 13735

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

Editar: J'ai créé ce wiki communautaire pour permettre plus d'édition.

Solution pour V8 (Chrome / Node.JS), fonctionne dans Firefox, et peut être modifié pour fonctionner à peu près correctement dans IE (voir la 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 "Show me the 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 aussi remplacer this.constructor con UserError et cela vous permet de déplacer le code en dehors de la fonction, afin qu'il ne soit appelé qu'une seule fois.

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

Cet avertissement ne s'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é des navigateurs

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

Internet Explorer échoue dans les cas suivants

  • Les erreurs n'ont pas err.stack pour commencer, donc "ce n'est pas ma faute".

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

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

    else
        this.toString = function () { return this.name + ': ' + this.message }
  • IE ne prendra pas en compte UserError pour être un instanceof Error à moins que vous n'exécutiez le programme suivant quelque temps avant throw UserError

    UserError.prototype = Error.prototype

17 votes

Je ne pense pas que Firefox dispose réellement de captureStackTrace. C'est une extension V8 et elle n'est pas définie dans Firefox pour moi, et je ne peux pas trouver de références sur le web sur le fait que Firefox la supporte. (Merci quand même !)

6 votes

Error.call(this) ne fait en effet rien puisqu'il renvoie à une erreur plutôt que de modifier this .

1 votes

UserError.prototype = Error.prototype est trompeuse. Cela ne fait pas de l'héritage, cela les rend la même classe .

20voto

Blaine Points 878

La réponse de Crescent Fresh, qui a reçu de nombreux votes, est trompeuse. Bien que ses avertissements ne soient pas valables, il existe d'autres limites qu'il n'aborde pas.

Premièrement, le raisonnement du paragraphe "Caveats :" de Crescent n'a pas de sens. L'explication sous-entend que coder "un tas de if (error instanceof MyError) else ..." est en quelque sorte fastidieux ou verbeux par rapport à plusieurs instructions catch. De multiples instructions instanceof dans un seul bloc catch sont tout aussi concises que de multiples instructions catch - un code propre et concis sans aucun artifice. Il s'agit d'un excellent moyen d'émuler l'excellente gestion des erreurs spécifiques aux sous-types jetables de Java.

En ce qui concerne "la propriété 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, copiez simplement le bloc de code commençant par "var MyError =", en remplaçant le mot "MyError" par "ErrorX". (Si vous voulez ajouter des méthodes personnalisées à votre sous-classe, suivez l'exemple de texte).

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

0 votes

Ai-je bien compris que si vous ne sous-classez pas et utilisez directement new Error(...), le nom du fichier et la ligne sont signalés correctement ? Et vous dites en gros que dans la pratique (réelle et pas seulement de type sexy ou décorative) la sous-classe Errors n'a aucun sens ?

6 votes

Cette réponse est un peu confuse car Crescent Fresh's a été supprimé !

0 votes

Est-ce toujours le cas ? developer.mozilla.org/fr/US/docs/Web/JavaScript/Référence/ Le numéro de ligne 2 n'est pas celui où le nouveau a été appelé

15voto

JoWie Points 1

Que pensez-vous de cette solution ?

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

throw new MyError("Oops!");

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

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

Cette opération permet de s'assurer que tous les attributs sont corrects, tels que la pile, le nom du fichier, le numéro de ligne, etc.

Il ne vous reste plus qu'à copier les attributs ou à définir des getters pour eux. Voici un exemple utilisant les 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();
};

1 votes

J'ai publié cette solution sous forme de paquet npm : npmjs.com/package/throwable

1 votes

Une solution incroyablement élégante, merci de la partager ! Une variante : new MyErr (arg1, arg2, new Error()) et dans le constructeur de MyErr nous utilisons Object.assign pour affecter les propriétés de la dernière arg à this

0 votes

J'aime ça. Vous contournez une limitation en utilisant l'encapsulation au lieu de l'héritage.

9voto

panzi Points 2061

Dans l'exemple ci-dessus Error.apply (également Error.call ) ne fait rien pour moi (Firefox 3.6/Chrome 5). J'utilise une solution de contournement :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X