admin管理员组

文章数量:1295661

In our NodeJS application, we define custom error classes by extending the default Error object:

"use strict";
const util = require("util");

function CustomError(message) {
    Error.call(this);
    Error.captureStackTrace(this, CustomError);
    this.message = message;
}

util.inherits(CustomError, Error);

This allows us to throw CustomError("Something"); with the stack trace showing up correctly, and both instanceof Error and instanceof CustomError working correctly.

However, for returning errors in our API (over HTTP), we want to convert the error to JSON. Calling JSON.stringify() on an Error results in "{}", which is obviously not really descriptive for the consumer.

To fix this, I thought of overriding CustomError.prototype.toJSON(), to return an object literal with the error name and message. JSON.stringify() would then just stringify this object and all would work great:

// after util.inherits call

CustomError.prototype.toJSON = () => ({
    name    : "CustomError",
    message : this.message
});

However, I quickly saw that this throws a TypeError: Cannot assign to read only property 'toJSON' of Error. Which might make sense as I'm trying to write to the prototype. So I changed the constructor instead:

function CustomError(message) {
    Error.call(this);
    Error.captureStackTrace(this, CustomError);
    this.message = message;
    this.toJSON = () => ({
        name    : "CustomError",
        message : this.message
    });
}

This way (I expected), the CustomError.toJSON function would be used and the CustomError.prototype.toJSON (from Error) would be ignored.

Unfortunately, this just throws the error upon object construction: Cannot assign to read only property 'toJSON' of CustomError.

Next I tried removing "use strict"; from the file, which sort of solved the problem in that no error was being thrown anymore, although the toJSON() function was not used by JSON.stringify() at all.

At this point I'm just desperate and just try random things. Eventually I end up with using Object.defineProperty() instead of directly assigning to this.toJSON:

function CustomError(message) {
    Error.call(this);
    Error.captureStackTrace(this, CustomError);
    this.message = message;
    Object.defineProperty(this, "toJSON", {
        value: () => ({
            name    : "CustomError",
            message : this.message
        })
    });

This works perfectly. In strict mode, no errors are being called, and JSON.stringify() returns {"name:" CustomError", "message": "Something"} like I want it to.

So although it works as I want it to now, I still want to know:

  1. Why does this work exactly? I expect it to be the equivalent to this.toJSON = ... but apparently it is not.
  2. Should it work like this? I.e. is it safe to depend on this behaviour?
  3. If not, how should I override the toJSON method correctly? (if possible at all)

In our NodeJS application, we define custom error classes by extending the default Error object:

"use strict";
const util = require("util");

function CustomError(message) {
    Error.call(this);
    Error.captureStackTrace(this, CustomError);
    this.message = message;
}

util.inherits(CustomError, Error);

This allows us to throw CustomError("Something"); with the stack trace showing up correctly, and both instanceof Error and instanceof CustomError working correctly.

However, for returning errors in our API (over HTTP), we want to convert the error to JSON. Calling JSON.stringify() on an Error results in "{}", which is obviously not really descriptive for the consumer.

To fix this, I thought of overriding CustomError.prototype.toJSON(), to return an object literal with the error name and message. JSON.stringify() would then just stringify this object and all would work great:

// after util.inherits call

CustomError.prototype.toJSON = () => ({
    name    : "CustomError",
    message : this.message
});

However, I quickly saw that this throws a TypeError: Cannot assign to read only property 'toJSON' of Error. Which might make sense as I'm trying to write to the prototype. So I changed the constructor instead:

function CustomError(message) {
    Error.call(this);
    Error.captureStackTrace(this, CustomError);
    this.message = message;
    this.toJSON = () => ({
        name    : "CustomError",
        message : this.message
    });
}

This way (I expected), the CustomError.toJSON function would be used and the CustomError.prototype.toJSON (from Error) would be ignored.

Unfortunately, this just throws the error upon object construction: Cannot assign to read only property 'toJSON' of CustomError.

Next I tried removing "use strict"; from the file, which sort of solved the problem in that no error was being thrown anymore, although the toJSON() function was not used by JSON.stringify() at all.

At this point I'm just desperate and just try random things. Eventually I end up with using Object.defineProperty() instead of directly assigning to this.toJSON:

function CustomError(message) {
    Error.call(this);
    Error.captureStackTrace(this, CustomError);
    this.message = message;
    Object.defineProperty(this, "toJSON", {
        value: () => ({
            name    : "CustomError",
            message : this.message
        })
    });

This works perfectly. In strict mode, no errors are being called, and JSON.stringify() returns {"name:" CustomError", "message": "Something"} like I want it to.

So although it works as I want it to now, I still want to know:

  1. Why does this work exactly? I expect it to be the equivalent to this.toJSON = ... but apparently it is not.
  2. Should it work like this? I.e. is it safe to depend on this behaviour?
  3. If not, how should I override the toJSON method correctly? (if possible at all)
Share edited Feb 1, 2017 at 19:49 tkers asked Sep 28, 2015 at 11:26 tkerstkers 9385 silver badges18 bronze badges 3
  • 1 Take a look at my answer here and see if that is what you're asking about. Thanks! – Qantas 94 Heavy Commented Sep 28, 2015 at 11:40
  • @Qantas94Heavy That's pretty weird behaviour indeed, but at least explains why I can't assign to the property normally. What I still don't get from the linked article in your ment: does Object.defineProperty work (i.e. override read-only prototype properties anyway) by design or is this unexpected behaviour upon I should not rely? – tkers Commented Sep 28, 2015 at 12:06
  • I'm getting a TypeError: Cannot redefine property: xxxx – Angelos Pikoulas Commented Oct 6, 2023 at 17:47
Add a ment  | 

3 Answers 3

Reset to default 4

Why does this work exactly?

Object.defineProperty just defines a property (or alters its attributes, if it already exists and is configurable). Unlike an assignment this.toJSON = … it does not care about any inheritance and does not check whether there is an inherited property that might be a setter or non-writable.

Should it work like this? I.e. is it safe to depend on this behaviour?

Yes, and yes. Probably you can even use it on the prototype.


For your actual use case, given a recent node.js version, use an ES6 class that extends Error for the best results.

Since I noticed you using the arrow function I'm going to assume you have access to ES6, meaning you have access to classes too.

You can just simply extend the Error class. For example:

class CustomError extends Error {
    toJSON() {
        return {
            name: 'CustomError',
            message: this.message
        };
    }
}

If you truly need to use ponents or other React features inside your <template> element you can fix the React to render in content by monkey-patching a few of DOM methods of HTMLTemplateElement.

HTMLTemplateElement.prototype.appendChild = function (child) {
  return this.content.appendChild(child);
}

HTMLTemplateElement.prototype.removeChild = function (child) {
  return this.content.removeChild(child);
}

Object.defineProperty(HTMLTemplateElement.prototype, "firstChild", {
  get() {
    return this.content.firstChild;
  }
})

I was able to render anything inside a template element after this. The appendChild and removeChild is mandatory because it will be used for render but if you're not using hydration you may not need the firstChild patch.

本文标签: javascriptObjectdefineProperty to override readonly propertyStack Overflow