web-dev-qa-db-fra.com

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 cours d'exécution, cela donne The message is: ''. Des idées quant à pourquoi ou s'il existe un meilleur moyen de créer une nouvelle sous-classe Error? Existe-t-il un problème avec applying pour le constructeur natif Error que je ne connais pas?

185
cdleary

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

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

Cependant, je voudrais simplement lancer votre propre objet et simplement vérifier la propriété name.

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

Modifier basé sur les commentaires

Après avoir regardé les commentaires et essayé de me rappeler pourquoi j'attribuerais 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 le "problème" que j'ai rencontré: la propriété de pile de l'erreur correspond au numéro de ligne où new Error() a été créé, et non à l'endroit où le throw e s'est produit. Cependant, cela peut être préférable d'avoir l'effet secondaire d'une ligne NotImplementedError.prototype.name = "NotImplementedError" affectant l'objet Error.

En outre, remarquez avec NotImplementedError2, lorsque je ne définis pas explicitement le .name, il est égal à "Erreur". Cependant, comme mentionné dans les commentaires, étant donné que cette version définit le prototype sur new Error(), je pourrais définir NotImplementedError2.prototype.name = "NotImplementedError2" et être OK.

183
Kevin Hakanson

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

Hériter de l'objet Error - où est la propriété du message?

TL; DR:

R. La raison pour laquelle message n'est pas défini est que Error est une fonction qui renvoie un nouvel objet Error et ne manipule pasthis de quelque manière que ce soit.

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

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 quelques astuces pour énumérer toutes les propriétés non énumérables de l'erreur tmp et les définir plutôt que de définir explicitement uniquement stack et message, mais cette astuce n'est pas prise en charge, c'est-à-dire <9

85
B T

Si quelqu'un est curieux de savoir comment créer une erreur personnalisée et obtenir la trace de pile:

function CustomError(message) {
  this.name = 'CustomError';
  this.message = message || '';
  var error = new Error(this.message);
  error.name = this.name;
  this.stack = error.stack;
}
CustomError.prototype = Object.create(Error.prototype);

try {
  throw new CustomError('foobar');
}
catch (e) {
  console.log('name:', e.name);
  console.log('message:', e.message);
  console.log('stack:', e.stack);
}
20
risto

Dans ES2015, vous pouvez utiliser class pour le faire proprement: 

class NotImplemented extends Error {
  constructor(message = "", ...args) {
    super(message, ...args);
    this.message = message + " has not yet been implemented.";
  }
}

Cela ne modifie pas le prototype global Error, vous permet de personnaliser message, name et d'autres attributs et de capturer correctement la pile. C'est aussi très lisible. 

Bien sûr, vous devrez peut-être utiliser un outil tel que babel si votre code sera exécuté sur des navigateurs plus anciens.

14
rattray

Cette section de la norme explique peut-être pourquoi l'appel Error.apply n'initialise pas l'objet:

15.11.1 Le constructeur d'erreur appelé en tant que fonction

Lorsque Error est appelé en tant que fonction plutôt qu'en tant que constructeur, il crée et initialise un nouvel objet Error. Ainsi, l'appel de fonction Error (...) est équivalent à l'expression de création d'objet nouvelle erreur (...) avec le mêmes arguments.

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

Tester avec le code suivant semble démontrer que c'est ce qui se passe réellement:

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 l’exécution:

returned.message = 'some message'
this.message = ''
7
Dave

J'ai eu un problème similaire à celui-ci. Mon erreur doit être une instanceof à la fois Error et NotImplemented, et elle doit également produire une trace cohérente 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 satisfait aux trois critères et, bien que la propriété stack ne soit pas standard, elle est prise en charge par la plupart des navigateurs récents , ce qui est acceptable dans mon cas. 

6
sinisterchipmunk
function InvalidValueError(value, type) {
    this.message = "Expected `" + type.name + "`: " + value;
    var error = new Error(this.message);
    this.stack = error.stack;
}
InvalidValueError.prototype = new Error();
InvalidValueError.prototype.name = InvalidValueError.name;
InvalidValueError.prototype.constructor = InvalidValueError;
6
Pierre Voisin

Accoring to Joyent vous ne devriez pas jouer avec la propriété stack (ce que je vois dans de nombreuses réponses données ici), car cela aurait un impact négatif sur les performances. Voici ce qu'ils disent:

stack: généralement, ne plaisante pas avec cela. Ne même pas l'augmenter. La V8 ne la calcule que si quelqu'un lit réellement la propriété, ce qui améliore considérablement les performances des erreurs manipulables. Si vous lisez la propriété juste pour l'augmenter, vous finirez par payer le coût même si votre appelant n'a pas besoin de la pile.

J'aime et je voudrais mentionner leur idée d’emballer l’erreur originale qui est un remplacement agréable de Nice pour la transmission de la pile.

Voici comment je crée une erreur personnalisée, compte tenu de ce qui précède:

version es5:

function RError(options) {
    options = options || {}; // eslint-disable-line no-param-reassign
    this.name = options.name;
    this.message = options.message;
    this.cause = options.cause;

    // capture stack (this property is supposed to be treated as private)
    this._err = new Error();

    // create an iterable chain
    this.chain = this.cause ? [this].concat(this.cause.chain) : [this];
}
RError.prototype = Object.create(Error.prototype, {
    constructor: {
        value: RError,
        writable: true,
        configurable: true
    }
});

Object.defineProperty(RError.prototype, 'stack', {
    get: function stack() {
        return this.name + ': ' + this.message + '\n' + this._err.stack.split('\n').slice(2).join('\n');
    }
});

Object.defineProperty(RError.prototype, 'why', {
    get: function why() {
        var _why = this.name + ': ' + this.message;
        for (var i = 1; i < this.chain.length; i++) {
            var e = this.chain[i];
            _why += ' <- ' + e.name + ': ' + e.message;
        }
        return _why;
    }
});

// usage

function fail() {
    throw new RError({
        name: 'BAR',
        message: 'I messed up.'
    });
}

function failFurther() {
    try {
        fail();
    } catch (err) {
        throw new RError({
            name: 'FOO',
            message: 'Something went wrong.',
            cause: err
        });
    }
}

try {
    failFurther();
} catch (err) {
    console.error(err.why);
    console.error(err.stack);
    console.error(err.cause.stack);
}

version es6:

class RError extends Error {
    constructor({name, message, cause}) {
        super();
        this.name = name;
        this.message = message;
        this.cause = cause;
    }
    [Symbol.iterator]() {
        let current = this;
        let done = false;
        const iterator = {
            next() {
                const val = current;
                if (done) {
                    return { value: val, done: true };
                }
                current = current.cause;
                if (!val.cause) {
                    done = true;
                }
                return { value: val, done: false };
            }
        };
        return iterator;
    }
    get why() {
        let _why = '';
        for (const e of this) {
            _why += `${_why.length ? ' <- ' : ''}${e.name}: ${e.message}`;
        }
        return _why;
    }
}

// usage

function fail() {
    throw new RError({
        name: 'BAR',
        message: 'I messed up.'
    });
}

function failFurther() {
    try {
        fail();
    } catch (err) {
        throw new RError({
            name: 'FOO',
            message: 'Something went wrong.',
            cause: err
        });
    }
}

try {
    failFurther();
} catch (err) {
    console.error(err.why);
    console.error(err.stack);
    console.error(err.cause.stack);
}

J'ai mis ma solution dans un module, le voici: https://www.npmjs.com/package/rerror

4
borisdiakur

J'ai utilisé le modèle de constructeur pour créer le nouvel objet d'erreur. J'ai défini la chaîne prototype telle qu'une instance Error. Voir le constructeur MDN Error reference.

Vous pouvez consulter cet extrait sur ce Gist.

LA MISE EN OEUVRE

// Creates user-defined exceptions
var CustomError = (function() {
  'use strict';

  //constructor
  function CustomError() {
    //enforces 'new' instance
    if (!(this instanceof CustomError)) {
      return new CustomError(arguments);
    }
    var error,
      //handles the arguments object when is passed by enforcing a 'new' instance
      args = Array.apply(null, typeof arguments[0] === 'object' ? arguments[0] : arguments),
      message = args.shift() || 'An exception has occurred';

    //builds the message with multiple arguments
    if (~message.indexOf('}')) {
      args.forEach(function(arg, i) {
        message = message.replace(RegExp('\\{' + i + '}', 'g'), arg);
      });
    }

    //gets the exception stack
    error = new Error(message);
    //access to CustomError.prototype.name
    error.name = this.name;

    //set the properties of the instance
    //in order to resemble an Error instance
    Object.defineProperties(this, {
      stack: {
        enumerable: false,
        get: function() { return error.stack; }
      },
      message: {
        enumerable: false,
        value: message
      }
    });
  }

  // Creates the prototype and prevents the direct reference to Error.prototype;
  // Not used new Error() here because an exception would be raised here,
  // but we need to raise the exception when CustomError instance is created.
  CustomError.prototype = Object.create(Error.prototype, {
    //fixes the link to the constructor (ES5)
    constructor: setDescriptor(CustomError),
    name: setDescriptor('JSU Error')
  });

  function setDescriptor(value) {
    return {
      configurable: false,
      enumerable: false,
      writable: false,
      value: value
    };
  }

  //returns the constructor
  return CustomError;
}());

USAGE

Le constructeur CustomError peut recevoir de nombreux arguments pour construire le message, par exemple .

var err1 = new CustomError("The url of file is required"),
    err2 = new CustomError("Invalid Date: {0}", +"date"),
    err3 = new CustomError("The length must be greater than {0}", 4),
    err4 = new CustomError("Properties .{0} and .{1} don't exist", "p1", "p2");

throw err4;

Et voici à quoi ressemble l'erreur personnalisée:

 Custom error prototype chain

2
jherax

Je devais juste mettre en œuvre quelque chose comme ça et constaté que la pile avait été perdue dans ma propre implémentation d'erreur. Ce que je devais faire était de créer une erreur factice et d'extraire la pile 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;
}
2
Jules

Ceci est ma mise en œuvre:

class HttpError extends Error {
  constructor(message, code = null, status = null, stack = null, name = null) {
    super();
    this.message = message;
    this.status = 500;

    this.name = name || this.constructor.name;
    this.code = code || `E_${this.name.toUpperCase()}`;
    this.stack = stack || null;
  }

  static fromObject(error) {
    if (error instanceof HttpError) {
      return error;
    }
    else {
      const { message, code, status, stack } = error;
      return new ServerError(message, code, status, stack, error.constructor.name);
    }
  }

  expose() {
    if (this instanceof ClientError) {
      return { ...this };
    }
    else {
      return {
        name: this.name,
        code: this.code,
        status: this.status,
      }
    }
  }
}

class ServerError extends HttpError {}

class ClientError extends HttpError { }

class IncorrectCredentials extends ClientError {
  constructor(...args) {
    super(...args);
    this.status = 400;
  }
}

class ResourceNotFound extends ClientError {
  constructor(...args) {
    super(...args);
    this.status = 404;
  }
}

Exemple d'utilisation # 1:

app.use((req, res, next) => {
  try {
    invalidFunction();
  }
  catch (err) {
    const error = HttpError.fromObject(err);
    return res.status(error.status).send(error.expose());
  }
});

Exemple d'utilisation # 2:

router.post('/api/auth', async (req, res) => {
  try {
    const isLogged = await User.logIn(req.body.username, req.body.password);

    if (!isLogged) {
      throw new IncorrectCredentials('Incorrect username or password');
    }
    else {
      return res.status(200).send({
        token,
      });
    }
  }
  catch (err) {
    const error = HttpError.fromObject(err);
    return res.status(error.status).send(error.expose());
  }
});
1
Rafal Enden

Le constructeur doit ressembler à une méthode d'usine et renvoyer ce que vous voulez. Si vous avez besoin de méthodes/propriétés supplémentaires, vous pouvez les ajouter à l'objet avant de le renvoyer.

function NotImplementedError(message) { return new Error("Not implemented", message); }

x = new NotImplementedError();

Bien que je ne sois pas sûr de la raison pour laquelle vous devriez faire cela. Pourquoi ne pas simplement utiliser new Error...? Les exceptions personnalisées n'ajoutent pas grand-chose en JavaScript (ou probablement à un langage non typé).

1
pluckyglen

Ceci est bien implémenté dans Cesium DeveloperError:

Dans sa forme simplifiée:

var NotImplementedError = function(message) {
    this.name = 'NotImplementedError';
    this.message = message;
    this.stack = (new Error()).stack;
}

// Later on...

throw new NotImplementedError();
1
Aram Kocharyan

Une autre alternative pourrait ne pas fonctionner dans tous les environnements.Au moins il est certain que cela fonctionne dans nodejs 0.8 Cette approche utilise une méthode non standard de modification du proto prop interne.

function myError(msg){ 
      var e = new Error(msg); 
      _this = this; 
      _this.__proto__.__proto__ = e;
}
0
Chandu

J'aime le faire comme ça, donc le message est le même dans un stacktrace ou un toString, et je peux passer juste un nom ou un nom et un message. Utile lorsque vous générez des erreurs HTTP, par exemple, vos gestionnaires ne peuvent que error.toString() et les traiter avec élégance.

class AppException extends Error {
  constructor(code, message) {
    const fullMsg = message ? `${code}: ${message}` : code;
    super(fullMsg);
    this.name = code;
    this.message = fullMsg;
  }
  
  toString() {
    return this.message;
  }
}

// Just an error name
try {
  throw new AppException('Forbidden');
} catch(e) {
  console.error(e);
  console.error(e.toString());
}

// A name and a message
try {
  throw new AppException('Forbidden', 'You don\'t have access to this page');
} catch(e) {
  console.error(e);
  console.error(e.toString());
}

0
Dominic

Si vous utilisez Node/Chrome. L'extrait suivant vous permettra d'obtenir l'extension qui répond aux exigences suivantes.

  • err instanceof Error
  • err instanceof CustomErrorType
  • console.log () renvoie [CustomErrorType] lorsqu'il est créé avec un message
  • console.log () renvoie [CustomErrorType: message] lorsqu'il est créé sans message
  • throw/stack fournit les informations au moment où l'erreur a été créée.
  • Fonctionne de manière optimale dans Node.JS et Chrome.
  • Passera les contrôles instance de Chrome, Safari, Firefox et IE 8+, mais n'aura pas de pile valide en dehors de Chrome/Safari. Je suis d'accord avec ça parce que je peux déboguer en chrome, mais le code qui nécessite des types d'erreur spécifiques fonctionnera toujours dans plusieurs navigateurs. Si vous avez uniquement besoin de Node, vous pouvez facilement supprimer les instructions if et vous êtes prêt à partir .

Fragment

var CustomErrorType = function(message) {
    if (Object.defineProperty) {
        Object.defineProperty(this, "message", {
            value : message || "",
            enumerable : false
        });
    } else {
        this.message = message;
    }

    if (Error.captureStackTrace) {
        Error.captureStackTrace(this, CustomErrorType);
    }
}

CustomErrorType.prototype = new Error();
CustomErrorType.prototype.name = "CustomErrorType";

Usage

var err = new CustomErrorType("foo");

Sortie

var err = new CustomErrorType("foo");
console.log(err);
console.log(err.stack);

[CustomErrorType: foo]
CustomErrorType: foo
    at Object.<anonymous> (/errorTest.js:27:12)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:906:3

/errorTest.js:30
        throw err;
              ^
CustomErrorType: foo
    at Object.<anonymous> (/errorTest.js:27:12)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:906:3
0
Nucleon

Au lieu de ne pouvoir utiliser instanceof, ce qui suit conserve la trace de la pile d'origine et n'utilise aucune astuce non standard.

// the function itself
var fixError = function(err, name) {
    err.name = name;
    return err;
}

// using the function
try {
    throw fixError(new Error('custom error message'), 'CustomError');
} catch (e) {
    if (e.name == 'CustomError')
        console.log('Wee! Custom Error! Msg:', e.message);
    else
        throw e; // unhandled. let it propagate upwards the call stack
}
0
Gima