undantag - Hur skapar jag ett eget fel i JavaScript?


Av någon anledning ser det ut som om konstruktördelegering inte fungerar i följande kodavsnitt:

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

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

Att köra detta ger The message is: ''. Några idéer om varför, eller om det finns ett bättre sätt att skapa en ny Error-undernklass? Finns det ett problem med att applya den inbyggda Error-konstruktorn som jag inte känner till?

Svar #1

Uppdatera din kod för att tilldela din prototyp till Error.prototype så att instanceof och dina assertioner fungerar.

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

Men jag skulle bara kasta ditt eget objekt och bara kontrollera namn-egenskapen.

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

Redigera baserat på kommentarer

Efter att ha tittat på kommentarerna och försökt komma ihåg varför jag skulle tilldela prototype till Error.prototype istället för new Error() som Nicholas Zakas gjorde i sin artikel, skapade jag en jsFiddle med koden nedan:

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);
}

Konsolutdata var detta.

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

Detta bekräftar att det "problem" jag stötte på var att felens stack-egenskap var radnumret där new Error() skapades, och inte där throw e inträffade. Dock kan det vara bättre än att ha bieffekten av en NotImplementedError.prototype.name="NotImplementedError"-rad som påverkar Error-objektet.

Observera också att med NotImplementedError2, när jag inte sätter .name explicit, är det lika med "Error". Men som nämnts i kommentarerna, eftersom den versionen sätter prototypen till new Error(), kunde jag sätta NotImplementedError2.prototype.name="NotImplementedError2" och det skulle vara okej.

Svar #2

I ES2015 kan du använda class för att göra detta på ett rent sätt:

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

Detta ändrar inte den globala felprototypen, låter dig anpassa meddelande, namn och andra attribut, och fångar stacken korrekt. Den är också ganska lättläst.

Självklart kan du behöva använda ett verktyg som babel om din kod ska köras på äldre webbläsare.

Svar #3

Alla ovanstående svar är fruktansvärt hemska - verkligen. Även det med 107 upp! Det riktiga svaret är här, grabbar:

Att ärva från Error-objektet - var är egenskapen message?

Sammanfattning:

A. Anledningen till att message inte sätts är att Error är en funktion som returnerar ett nytt Error-objekt och manipulerar inte this på något sätt.

B. Sättet att göra detta rätt är att returnera resultatet av apply från konstruktorn, samt att sätta prototypen på det vanliga komplicerade javascript-sättet:

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();
*/

Du skulle förmodligen kunna använda lite trikseri för att uppräkna alla icke-uppräkningsbara egenskaper hos tmp Error för att sätta dem istället för att uttryckligen bara sätta stack och message, men trikseriet stöds inte i ie<9

Svar #4

Om någon är nyfiken på hur man skapar ett anpassat fel och får stackspåret:

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);
}

Svar #5

class NotImplementedError extends Error {
  constructor(message) {
    super(message);
    this.message = message;
  }
}
NotImplementedError.prototype.name = 'NotImplementedError';
module.exports = NotImplementedError;

och

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);
}

Det är bara en klassrepresentation av detta svar.

utdata

NotImplementedError: NotImplementedError message
  ...stacktrace
ex1 instanceof NotImplementedError = true
ex1 instanceof Error = true
ex1.name = NotImplementedError
ex1.message = NotImplementedError message

Svar #6

Jag gillar att göra det så här:

  • Använd namnet så att toString() kastar "{code}: {message}"
  • Bifoga kod till error.code eftersom det är bättre att kontrollera/parsa en kod i kod än att kontrollera ett meddelande, som du kanske vill lokalisera till exempel
  • Bifoga meddelande till error.message som ett alternativ till error.toString()

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

// Just a code
try {
  throw new AppException('FORBIDDEN');
} catch(e) {
  console.error(e);
  console.error(e.toString());
  console.log(e.code === 'FORBIDDEN');
}

// A code 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());
  console.log(e.code === 'FORBIDDEN');
}

Relaterade artiklar