Type d'exception personnalisé

224

Puis-je définir un type personnalisé pour les exceptions définies par l'utilisateur en JavaScript? Si oui, comment le ferais-je?

Manki
la source
3
Il faut se méfier. Selon JavaScript en 10 minutes, vous n'obtiendrez pas de trace de pile si vous jetez une valeur sans boîte.
Janus Troelsen
exceptionsjs.com offre la possibilité de créer des exceptions personnalisées et fournit certaines exceptions manquantes, notamment ArgumentException et NotImplemented par défaut.
Steven Wexler

Réponses:

232

De WebReference :

throw { 
  name:        "System Error", 
  level:       "Show Stopper", 
  message:     "Error detected. Please contact the system administrator.", 
  htmlMessage: "Error detected. Please contact the <a href=\"mailto:[email protected]\">system administrator</a>.",
  toString:    function(){return this.name + ": " + this.message;} 
}; 
jon077
la source
7
@ b.long C'est dans "JavaScript: The Good Parts" (grand livre IMO). Cet aperçu de Google Livres montre la section: books.google.com/books?id=PXa2bby0oQ0C&pg=PA32&lpg=PA32
orip
11
L'ajout d'une méthode toString la fera bien apparaître dans la console javascript. sans cela, il apparaît comme: Uncaught # <Object> avec la chaîne toString, il apparaît comme: Uncaught System Error: Erreur détectée. Veuillez contacter l'administrateur système.
JDC
11
Cela ne vous permettra pas d'empiler des traces à moins que vous n'héritiez d'une erreur
Luke H
Comment pouvez-vous filtrer dans un bloc catch pour ne travailler qu'avec cette erreur personnalisée?
Overdrivr
@overdrivr quelque chose comme catch (e) { if (e instanceof TypeError) { … } else { throw e; } }⦃⦄ ou catch (e) { switch (e.constructor) { case TypeError: …; break; default: throw e; }⦃⦄.
sam boosalis
92

Vous devez créer une exception personnalisée qui hérite de manière prototype d'Erreur. Par exemple:

function InvalidArgumentException(message) {
    this.message = message;
    // Use V8's native method if available, otherwise fallback
    if ("captureStackTrace" in Error)
        Error.captureStackTrace(this, InvalidArgumentException);
    else
        this.stack = (new Error()).stack;
}

InvalidArgumentException.prototype = Object.create(Error.prototype);
InvalidArgumentException.prototype.name = "InvalidArgumentException";
InvalidArgumentException.prototype.constructor = InvalidArgumentException;

Ceci est essentiellement une version simplifiée de ce disfated affiché ci-dessus avec l'amélioration que les traces de pile fonctionnent sur Firefox et d'autres navigateurs. Il satisfait aux mêmes tests qu'il a postés:

Usage:

throw new InvalidArgumentException();
var err = new InvalidArgumentException("Not yet...");

Et il va se comporter est attendu:

err instanceof InvalidArgumentException          // -> true
err instanceof Error                             // -> true
InvalidArgumentException.prototype.isPrototypeOf(err) // -> true
Error.prototype.isPrototypeOf(err)               // -> true
err.constructor.name                             // -> InvalidArgumentException
err.name                                         // -> InvalidArgumentException
err.message                                      // -> Not yet...
err.toString()                                   // -> InvalidArgumentException: Not yet...
err.stack                                        // -> works fine!
asselin
la source
80

Vous pouvez implémenter vos propres exceptions et leur gestion par exemple comme ici:

// define exceptions "classes" 
function NotNumberException() {}
function NotPositiveNumberException() {}

// try some code
try {
    // some function/code that can throw
    if (isNaN(value))
        throw new NotNumberException();
    else
    if (value < 0)
        throw new NotPositiveNumberException();
}
catch (e) {
    if (e instanceof NotNumberException) {
        alert("not a number");
    }
    else
    if (e instanceof NotPositiveNumberException) {
        alert("not a positive number");
    }
}

Il existe une autre syntaxe pour intercepter une exception typée, bien que cela ne fonctionne pas dans tous les navigateurs (par exemple pas dans IE):

// define exceptions "classes" 
function NotNumberException() {}
function NotPositiveNumberException() {}

// try some code
try {
    // some function/code that can throw
    if (isNaN(value))
        throw new NotNumberException();
    else
    if (value < 0)
        throw new NotPositiveNumberException();
}
catch (e if e instanceof NotNumberException) {
    alert("not a number");
}
catch (e if e instanceof NotPositiveNumberException) {
    alert("not a positive number");
}
Sergey Ilinsky
la source
2
Le site Web de MSN affiche cet avertissement concernant les captures de condition: Non standard Cette fonctionnalité n'est pas standard et n'est pas conforme aux normes. Ne l'utilisez pas sur des sites de production tournés vers le Web: cela ne fonctionnera pas pour tous les utilisateurs. Il peut également y avoir de grandes incompatibilités entre les implémentations et le comportement peut changer à l'avenir.
Lawrence Dol
40

Oui. Vous pouvez lancer tout ce que vous voulez: des entiers, des chaînes, des objets, peu importe. Si vous souhaitez lancer un objet, créez simplement un nouvel objet, comme vous le feriez dans d'autres circonstances, puis lancez-le. La référence Javascript de Mozilla a plusieurs exemples.

Rob Kennedy
la source
26
function MyError(message) {
 this.message = message;
}

MyError.prototype = new Error;

Cela permet une utilisation comme ..

try {
  something();
 } catch(e) {
  if(e instanceof MyError)
   doSomethingElse();
  else if(e instanceof Error)
   andNowForSomethingCompletelyDifferent();
}
Morgan ARR Allen
la source
Ce bref exemple ne fonctionnerait-il pas exactement de la même manière même si vous n'avez pas hérité du prototype d'Erreur? Je ne sais pas ce que cela vous rapporte dans cet exemple.
EleventyOne
1
Non, ce e instanceof Errorserait faux.
Morgan ARR Allen
En effet. Mais puisque e instanceof MyErrorcela serait vrai, l' else if(e instanceof Error)énoncé ne serait jamais évalué.
EleventyOne
À droite, ceci est juste un exemple de la façon dont ce style de try / catch fonctionnerait. Où else if(e instanceof Error)serait la dernière prise. Probablement suivi d'un simple else(que je n'ai pas inclus). Un peu comme default:dans une instruction switch mais pour les erreurs.
Morgan ARR Allen
15

En bref:

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 ES5 pur :

    function CustomError(message, fileName, lineNumber) {
      const 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
11

Voici comment vous pouvez créer des erreurs personnalisées avec un comportement complètement identique au natif Error. Cette technique ne fonctionne que dans Chrome et node.js pour l'instant. Je ne recommanderais pas non plus de l'utiliser si vous ne comprenez pas ce qu'il fait.

Error.createCustromConstructor = (function() {

    function define(obj, prop, value) {
        Object.defineProperty(obj, prop, {
            value: value,
            configurable: true,
            enumerable: false,
            writable: true
        });
    }

    return function(name, init, proto) {
        var CustomError;
        proto = proto || {};
        function build(message) {
            var self = this instanceof CustomError
                ? this
                : Object.create(CustomError.prototype);
            Error.apply(self, arguments);
            Error.captureStackTrace(self, CustomError);
            if (message != undefined) {
                define(self, 'message', String(message));
            }
            define(self, 'arguments', undefined);
            define(self, 'type', undefined);
            if (typeof init == 'function') {
                init.apply(self, arguments);
            }
            return self;
        }
        eval('CustomError = function ' + name + '() {' +
            'return build.apply(this, arguments); }');
        CustomError.prototype = Object.create(Error.prototype);
        define(CustomError.prototype, 'constructor', CustomError);
        for (var key in proto) {
            define(CustomError.prototype, key, proto[key]);
        }
        Object.defineProperty(CustomError.prototype, 'name', { value: name });
        return CustomError;
    }

})();

Comme résultat, nous obtenons

/**
 * name   The name of the constructor name
 * init   User-defined initialization function
 * proto  It's enumerable members will be added to 
 *        prototype of created constructor
 **/
Error.createCustromConstructor = function(name, init, proto)

Ensuite, vous pouvez l'utiliser comme ceci:

var NotImplementedError = Error.createCustromConstructor('NotImplementedError');

Et utilisez NotImplementedErrorcomme vous le feriez Error:

throw new NotImplementedError();
var err = new NotImplementedError();
var err = NotImplementedError('Not yet...');

Et il va se comporter est attendu:

err instanceof NotImplementedError               // -> true
err instanceof Error                             // -> true
NotImplementedError.prototype.isPrototypeOf(err) // -> true
Error.prototype.isPrototypeOf(err)               // -> true
err.constructor.name                             // -> NotImplementedError
err.name                                         // -> NotImplementedError
err.message                                      // -> Not yet...
err.toString()                                   // -> NotImplementedError: Not yet...
err.stack                                        // -> works fine!

Notez que cela error.stackfonctionne parfaitement et n'inclura pas d' NotImplementedErrorappel constructeur (grâce aux v8Error.captureStackTrace() ).

Remarque. Il est moche eval(). La seule raison pour laquelle il est utilisé est d'être correct err.constructor.name. Si vous n'en avez pas besoin, vous pouvez tout simplifier un peu.

déçu
la source
2
Error.apply(self, arguments)est spécifié pour ne pas fonctionner . Je suggère plutôt de copier la trace de la pile qui est compatible avec tous les navigateurs.
Kornel
11

J'utilise souvent une approche avec héritage prototypique. PrimordialtoString() vous donne l'avantage que des outils comme Firebug enregistreront les informations réelles au lieu de[object Object] sur la console pour les exceptions non détectées.

Utilisation instanceof pour déterminer le type d'exception.

main.js

// just an exemplary namespace
var ns = ns || {};

// include JavaScript of the following
// source files here (e.g. by concatenation)

var someId = 42;
throw new ns.DuplicateIdException('Another item with ID ' +
    someId + ' has been created');
// Firebug console:
// uncaught exception: [Duplicate ID] Another item with ID 42 has been created

Exception.js

ns.Exception = function() {
}

/**
 * Form a string of relevant information.
 *
 * When providing this method, tools like Firebug show the returned 
 * string instead of [object Object] for uncaught exceptions.
 *
 * @return {String} information about the exception
 */
ns.Exception.prototype.toString = function() {
    var name = this.name || 'unknown';
    var message = this.message || 'no description';
    return '[' + name + '] ' + message;
};

DuplicateIdException.js

ns.DuplicateIdException = function(message) {
    this.name = 'Duplicate ID';
    this.message = message;
};

ns.DuplicateIdException.prototype = new ns.Exception();
Matthias
la source
8

ES6

Avec la nouvelle classe et les mots clés étendus, c'est désormais beaucoup plus simple:

class CustomError extends Error {
  constructor(message) {
    super(message);
    //something
  }
}
Brunno
la source
7

Utilisez le lancer instruction .

JavaScript ne se soucie pas du type d'exception (comme le fait Java). JavaScript remarque juste, il y a une exception et quand vous l'attrapez, vous pouvez "regarder" ce que l'exception "dit".

Si vous devez lancer différents types d'exceptions, je vous suggère d'utiliser des variables qui contiennent la chaîne / l'objet de l'exception, c'est-à-dire le message. Lorsque vous en avez besoin, utilisez "throw myException" et dans la capture, comparez l'exception capturée à myException.

Xn0vv3r
la source
1

Voir cet exemple dans le MDN.

Si vous devez définir plusieurs erreurs (testez le code ici !):

function createErrorType(name, initFunction) {
    function E(message) {
        this.message = message;
        if (Error.captureStackTrace)
            Error.captureStackTrace(this, this.constructor);
        else
            this.stack = (new Error()).stack;
        initFunction && initFunction.apply(this, arguments);
    }
    E.prototype = Object.create(Error.prototype);
    E.prototype.name = name;
    E.prototype.constructor = E;
    return E;
}
var InvalidStateError = createErrorType(
    'InvalidStateError',
    function (invalidState, acceptedStates) {
        this.message = 'The state ' + invalidState + ' is invalid. Expected ' + acceptedStates + '.';
    });

var error = new InvalidStateError('foo', 'bar or baz');
function assert(condition) { if (!condition) throw new Error(); }
assert(error.message);
assert(error instanceof InvalidStateError);  
assert(error instanceof Error); 
assert(error.name == 'InvalidStateError');
assert(error.stack);
error.message;

Le code est principalement copié à partir de: Quel est un bon moyen d'étendre l'erreur en JavaScript?

Peter Tseng
la source
1

Une alternative à la réponse d' asselin pour une utilisation avec les classes ES2015

class InvalidArgumentException extends Error {
    constructor(message) {
        super();
        Error.captureStackTrace(this, this.constructor);
        this.name = "InvalidArgumentException";
        this.message = message;
    }
}
M. Tsjolder
la source
1
//create error object
var error = new Object();
error.reason="some reason!";

//business function
function exception(){
    try{
        throw error;
    }catch(err){
        err.reason;
    }
}

Maintenant, nous définissons ajouter la raison ou les propriétés que nous voulons à l'objet d'erreur et le récupérer. En rendant l'erreur plus raisonnable.

maté
la source