242 votes

Comment créer une erreur personnalisée en JavaScript ?

Pour une raison quelconque, il semble que la délégation de constructeur ne fonctionne pas dans l'extrait suivant :

function NotImplementedError() { 
  Error.apply(this, arguments); 
}
NotImplementedError.prototype = new Error();

var nie = new NotImplementedError("some message");
console.log("The message is: '"+nie.message+"'")

En l'exécutant, on obtient The message is: '' . Avez-vous une idée de la raison de cette situation ou existe-t-il un meilleur moyen de créer un nouveau site Web ? Error sous-classe ? Y a-t-il un problème avec apply à l'indigène Error dont je ne connais pas l'existence ?

0 votes

L'assertion nie instanceof NotImplementedError fonctionne-t-elle après vos modifications ? Je pensais que pour que cela fonctionne, vous deviez définir NotImplementedError.prototype.constructor explicitement.

0 votes

La prochaine fois, veuillez supprimer tout le code superflu qui n'est pas nécessaire à la démonstration de votre problème. En outre, wtc est js.jar ? Est-ce nécessaire pour reproduire le problème ?

2 votes

J'ai modifié cette question pour qu'elle soit compréhensible en 10 secondes plutôt qu'en 10 minutes.

216voto

Kevin Hakanson Points 15498

Mettez à jour votre code pour assigner votre prototype à Error.prototype et l'instanceof et vos assertions fonctionnent.

function NotImplementedError(message = "") {
    this.name = "NotImplementedError";
    this.message = message;
}
NotImplementedError.prototype = Error.prototype;

Cependant, je me contenterais de lancer votre propre objet et de vérifier la propriété du nom.

throw {name : "NotImplementedError", message : "too lazy to implement"}; 

Modifier en fonction des commentaires

Après avoir regardé les commentaires et essayé de me rappeler pourquoi j'aurais attribué le prototype à Error.prototype au lieu de new Error() comme Nicholas Zakas l'a fait dans son article J'ai créé un jsFiddle avec le code ci-dessous :

function NotImplementedError(message = "") {
  this.name = "NotImplementedError";
  this.message = message;
}
NotImplementedError.prototype = Error.prototype;

function NotImplementedError2(message = "") {
  this.message = message;
}
NotImplementedError2.prototype = new Error();

try {
  var e = new NotImplementedError("NotImplementedError message");
  throw e;
} catch (ex1) {
  console.log(ex1.stack);
  console.log("ex1 instanceof NotImplementedError = " + (ex1 instanceof NotImplementedError));
  console.log("ex1 instanceof Error = " + (ex1 instanceof Error));
  console.log("ex1.name = " + ex1.name);
  console.log("ex1.message = " + ex1.message);
}

try {
  var e = new NotImplementedError2("NotImplementedError2 message");
  throw e;
} catch (ex1) {
  console.log(ex1.stack);
  console.log("ex1 instanceof NotImplementedError2 = " + (ex1 instanceof NotImplementedError2));
  console.log("ex1 instanceof Error = " + (ex1 instanceof Error));
  console.log("ex1.name = " + ex1.name);
  console.log("ex1.message = " + ex1.message);
}

La sortie de la console était la suivante.

undefined
ex1 instanceof NotImplementedError = true
ex1 instanceof Error = true
ex1.name = NotImplementedError
ex1.message = NotImplementedError message
Error
    at window.onload (http://fiddle.jshell.net/MwMEJ/show/:29:34)
ex1 instanceof NotImplementedError2 = true
ex1 instanceof Error = true
ex1.name = Error
ex1.message = NotImplementedError2 message

Cela confirme que le "problème" que j'ai rencontré était la propriété de la pile de l'erreur était le numéro de ligne où new Error() a été créé, et non l'endroit où le throw e survenu. Cependant, c'est peut-être mieux que d'avoir l'effet secondaire d'un NotImplementedError.prototype.name = "NotImplementedError" ligne affectant l'objet Erreur.

De même, remarquez avec NotImplementedError2 lorsque je n'ai pas défini l'option .name explicitement, il est égal à "Error". Cependant, comme mentionné dans les commentaires, parce que cette version définit le prototype à new Error() je pourrais mettre NotImplementedError2.prototype.name = "NotImplementedError2" et être OK.

1 votes

L'objet créé à l'aide d'un objet littéral n'est pas une erreur, de sorte que la console d'erreur de Firefox (et, je suppose, celle des autres navigateurs également) ne saura pas comment la traiter de manière utile si elle n'est pas détectée. J'aime beaucoup mieux votre première idée.

0 votes

D'un autre côté, si votre code est configuré de telle sorte que vous SAVEZ qu'il sera attrapé (parce que vous ne produisez cette "erreur" que dans une fonction particulière, et que cette fonction est TOUJOURS enveloppée dans un try/catch), alors peut-être est-ce une bonne idée.

47 votes

Meilleure réponse, mais en prenant Error.prototype directement est probablement mal formulé. Si vous souhaitez par la suite ajouter un NotImplementedError.prototype.toString l'objet est maintenant aliasé à Error.prototype.toString -- mieux à faire NotImplementedError.prototype = new Error() .

88voto

B T Points 4868

Toutes les réponses ci-dessus sont terribles, vraiment terribles. Même celle avec 107 ups ! La vraie réponse est ici les gars :

Héritage de l'objet Error - où se trouve la propriété message ?

TL;DR :

A. La raison message n'est pas réglé est que Error est une fonction qui renvoie un nouvel objet Erreur et fait pas manipuler this de quelque manière que ce soit.

B. Pour bien faire les choses, il faut renvoyer le résultat de l'application depuis le constructeur, ainsi que définir le prototype de la manière compliquée habituelle de javascripty :

function MyError() {
    var temp = Error.apply(this, arguments);
    temp.name = this.name = 'MyError';
    this.message = temp.message;
    if(Object.defineProperty) {
        // getter for more optimizy goodness
        /*this.stack = */Object.defineProperty(this, 'stack', { 
            get: function() {
                return temp.stack
            },
            configurable: true // so you can change it if you want
        })
    } else {
        this.stack = temp.stack
    }
}
//inherit prototype using ECMAScript 5 (IE 9+)
MyError.prototype = Object.create(Error.prototype, {
    constructor: {
        value: MyError,
        writable: true,
        configurable: true
    }
});

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

//for EMCAScript 4 or ealier (IE 8 or ealier), inherit prototype this way instead of above code:
/*
var IntermediateInheritor = function() {};
IntermediateInheritor.prototype = Error.prototype;
MyError.prototype = new IntermediateInheritor();
*/

Vous pourriez probablement faire un tour de passe-passe pour énumérer toutes les propriétés non énumérables de l'objet de l'enquête. tmp Erreur pour les définir plutôt que de définir explicitement seulement stack et message mais l'astuce n'est pas supportée dans ie<9

0 votes

Mais votre solution ne définit pas correctement le nom de l'exception - la trace de pile contiendra 'Error' au lieu de 'MyError'.

0 votes

C'est réparé ! Je suppose que instanceof ne fonctionne pas comme prévu - c'est une instanceof Error, mais pas de MyError... hmm... Une idée, quelqu'un ?

0 votes

Ok, j'ai résolu le problème d'instanceof avec un peu d'aide de cette source : blog.getify.com/howto-custom-error-types-in-javascript

8voto

Dave Points 2554

Cette section de la norme peut expliquer pourquoi le Error.apply n'initialise pas l'objet :

15.11.1 Le constructeur d'erreur appelé comme une fonction

Lorsque Error est appelé comme une fonction plutôt que comme un constructeur, il crée et initialise un nouvel objet Error. initialise un nouvel objet Error. Ainsi, l'appel de la fonction Error(...) est équivalent à l'expression de création d'objet new Error(...) avec les mêmes arguments. mêmes arguments.

Dans ce cas, le Error détermine probablement qu'elle n'est pas appelée en tant que constructeur, et renvoie donc une nouvelle instance d'erreur au lieu d'initialiser la fonction this objet.

Les tests effectués avec le code suivant semblent démontrer que c'est bien ce qui se passe :

function NotImplementedError() { 
   var returned = Error.apply(this, arguments);
   console.log("returned.message = '" + returned.message + "'");
   console.log("this.message = '" + this.message + "'");
}
NotImplementedError.prototype = new Error();

var nie = new NotImplementedError("some message");

La sortie suivante est générée lors de cette exécution :

returned.message = 'some message'
this.message = ''

0 votes

Comment pourrait-on simuler cela avec une classe d'erreur personnalisée ? Par exemple, comment ma classe d'erreurs personnalisée pourrait-elle être utilisée à la fois comme une fonction qui crée une instance et comme un constructeur ?

0 votes

Non, ce n'est pas vrai. S'il retournait une nouvelle instance d'erreur, alors sa propriété msg fonctionnerait.

0 votes

Comment la propriété msg de la nouvelle instance affecte-t-elle la propriété msg de la nouvelle instance ? this sur Error.apply(this, arguments); ? Je dis que l'appel à l'erreur ici construit un nouvel objet, qui est jeté ; pas l'initialisation de l'objet déjà construit qui est assigné à nie .

6voto

sinisterchipmunk Points 1248

J'ai eu un problème similaire à celui-ci. Mon erreur doit être une instanceof les deux Error et NotImplemented et il doit également produire un backtrace cohérent dans la console.

Ma solution :

var NotImplemented = (function() {
  var NotImplemented, err;
  NotImplemented = (function() {
    function NotImplemented(message) {
      var err;
      err = new Error(message);
      err.name = "NotImplemented";
      this.message = err.message;
      if (err.stack) this.stack = err.stack;
    }
    return NotImplemented;
  })();
  err = new Error();
  err.name = "NotImplemented";
  NotImplemented.prototype = err;

  return NotImplemented;
}).call(this);

// TEST:
console.log("instanceof Error: " + (new NotImplemented() instanceof Error));
console.log("instanceof NotImplemented: " + (new NotImplemented() instanceofNotImplemented));
console.log("message: "+(new NotImplemented('I was too busy').message));
throw new NotImplemented("just didn't feel like it");

Résultat de l'exécution avec node.js :

instanceof Error: true
instanceof NotImplemented: true
message: I was too busy

/private/tmp/t.js:24
throw new NotImplemented("just didn't feel like it");
      ^
NotImplemented: just didn't feel like it
    at Error.NotImplemented (/Users/colin/projects/gems/jax/t.js:6:13)
    at Object.<anonymous> (/Users/colin/projects/gems/jax/t.js:24:7)
    at Module._compile (module.js:449:26)
    at Object.Module._extensions..js (module.js:467:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.runMain (module.js:487:10)
    at process.startup.processNextTick.process._tickCallback (node.js:244:9)

L'erreur répond à mes trois critères et, bien que l'erreur de l'utilisateur ne soit pas corrigée, elle n'a pas été corrigée. stack n'est pas standard, il est pris en charge par la plupart des navigateurs récents ce qui est acceptable dans mon cas.

2voto

Jules Points 1527

J'ai dû implémenter quelque chose comme ça et j'ai découvert que la pile était perdue dans ma propre implémentation d'erreur. Ce que j'ai dû faire, c'est créer une erreur fictive et récupérer la pile à partir de celle-ci :

My.Error = function (message, innerException) {
    var err = new Error();
    this.stack = err.stack; // IMPORTANT!
    this.name = "Error";
    this.message = message;
    this.innerException = innerException;
}
My.Error.prototype = new Error();
My.Error.prototype.constructor = My.Error;
My.Error.prototype.toString = function (includeStackTrace) {
    var msg = this.message;
    var e = this.innerException;
    while (e) {
        msg += " The details are:\n" + e.message;
        e = e.innerException;
    }
    if (includeStackTrace) {
        msg += "\n\nStack Trace:\n\n" + this.stack;
    }
    return msg;
}

0 votes

Cela ne règle pas le message

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