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?
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?
Réponses:
Le seul objet d'erreur de champ standard est la message
proprié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 stack
propriété, mais fileName
etlineNumber
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 stack
propriété, mais le throw
mot clé définit sourceURL
etline
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 .
function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
MyError.prototype.constructor = MyError
aussi.
this
, non?
Dans ES6:
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
this.name = this.constructor.name;
plutôt.
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
, DOM
ou 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 instanceof
problè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 instanceof
problè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
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 const
et ne gère pas les arguments personnalisés.
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.
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.constructor
par UserError
et 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.stack
pas 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)
toString
cesse 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é UserError
comme un instanceof Error
sauf si vous exécutez la commande suivante un certain temps avantthrow UserError
UserError.prototype = Error.prototype
Error.call(this)
ne fait en effet rien car il retourne une erreur plutôt que de la modifier this
.
UserError.prototype = Error.prototype
est trompeur. Cela ne fait pas d'héritage, cela fait d'eux la même classe .
Object.setPrototypeOf(this.constructor.prototype, Error.prototype)
c'est préférable this.constructor.prototype.__proto__ = Error.prototype
, du moins pour les navigateurs actuels.
Pour éviter le passe- partout pour chaque type d'erreur, j'ai combiné la sagesse de certaines des solutions en une createErrorType
fonction:
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';
});
this.name = name;
?
name
c'est déjà installé sur le prototype, ce n'est plus nécessaire. Je l'ai retiré. Merci!
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.
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 .prototype
propriété sur le constructeur (comme vous le faites dans le else
bloc pour les navigations qui n'en ont pas setPrototypeOf
).
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
.
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/…
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.
Object.setPrototypeOf
n'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
).
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é inherits
du util
module ( 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 captureStackTrace
méthode, qui crée une stack
propriété sur l'objet cible (dans ce cas, l' CustomError
instanciation). Pour plus de détails sur son fonctionnement, consultez la documentation ici .
this.message = this.message;
est-ce mal ou y a-t-il encore des choses folles que je ne connais pas sur JS?
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.
Crescent Fresh's
a été supprimée!
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();
};
new MyErr (arg1, arg2, new Error())
et dans le constructeur MyErr, nous utilisons Object.assign
pour assigner les propriétés du dernier arg àthis
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;
}
}
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;
};
throw CustomError('err')
au lieu dethrow new CustomError('err')
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';
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);
}
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)
Mes 2 cents:
a) Parce que l'accès à la Error.stack
proprié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/
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.
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.
É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.
m = new InvalidInputError(); dontThrowMeYet(m);
m = new ...
alors Promise.reject(m)
. Ce n'est pas une nécessité, mais le code est plus facile à lire.
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 Error
directement 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.
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
stack
et message
ne sont pas incluses dansMyError
etLe 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 () ??).
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)
}
}
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
}
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"; ...
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.captureStackTrace
en charge dans d'autres environnements.
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 }"
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 SignatureError
si 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 new
est facultative lors de la construction d'une erreur personnalisée.
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
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");
this.name = 'MyError'
extérieur de la fonction et la modifierMyError.prototype.name = 'MyError'
.