Vitamin
Vitamin

Reputation: 1526

Error.prototype.toString() will throw a TypeError if I define the "name" property with Object.defineProperty()

I've reduced the code to a minimum:

var util = require('util');

function ErrorDecorator(error) {
    Error.call(this);
    this.error = error;
}

util.inherits(ErrorDecorator, Error);

Object.defineProperty(ErrorDecorator.prototype, 'name', {
    configurable: false,
    enumerable: true,
    get: function () {
        return this.error.name;
    }
});

var e = new ErrorDecorator(new Error());

// TypeError: Cannot read property 'name' of undefined
console.log(e.toString());

Edit

I've come across an answer to another question which suggests that I use Object.defineProperty in the constructor instead of on the prototype. I do not fully understand why, but I can only assume that when I define name on the prototype that it's called "statically" and not with the instance as its context?

TLDR this code works:

var util = require('util');

function ErrorDecorator(error) {
    Error.call(this);
    this.error = error;
    Object.defineProperty(this, 'name', {
        configurable: false,
        enumerable: true,
        get: function () {
            return this.error.name;
        }
    });
}

util.inherits(ErrorDecorator, Error);

var e = new ErrorDecorator(new Error());

// Error
console.log(e.toString());

Edit 2

Even though I've found a solution I'm still interested in knowing why my first code example doesn't work, and if what I'm doing is against best practices.

Upvotes: 3

Views: 678

Answers (1)

raina77ow
raina77ow

Reputation: 106453

Consider the following:

var x = new Error();
var y = Object.create(x);
y.props = {
  name: 42
};
var z = Object.create(y);
Object.defineProperty(y, 'name', {
    configurable: false,
    enumerable: true,
    get: function () {
        console.log('Is it Z?', this === z);
        console.log('Is it Y?', this === y);
        return this.props.name;
    }
});
console.log(z.toString());

It's tricky: when toString is called on an object that a) doesn't have its own implementation of toString and b) has Error instance in its prototype chain, it follows the algorithm described in the standard:

15.11.4.4 Error.prototype.toString ( )

The following steps are taken:

  • Let O be the this value.
  • If Type(O) is not Object, throw a TypeError exception.
  • Let name be the result of calling the [[Get]] internal method of O with argument "name".
  • If name is undefined, then let name be "Error"; else let name be ToString(name).

The problem is that for some reason nodejs (0.10.33) seems to make a step up the prototype chain when in that getter: the results are false and true accordingly. Both Firefox and Chrome correctly use z as getter context.

Upvotes: 3

Related Questions