Next: , Previous: , Up: Inheritance   [Contents]


2.2.7 Error Subtypes

Extending ECMAScript’s built-in Error type is a bit cumbersome (to say the least)—it involves not only the traditional prototype chain, but also setting specific properties within the constructor. Further, different environments support different features (e.g. stack traces and column numbers), and values are relative to the stack frame of the Error subtype constructor itself.

With GNU ease.js, error subtyping is transparent:

    var MyError = Class( 'MyError' )
        .extend( Error, {} );

    var e = MyError( 'Foo' );
    e.message;  // Foo
    e.name;     // MyError

    // -- if supported by environment --
    e.stack;         // stack beginning at caller
    e.fileName;      // caller filename
    e.lineNumber;    // caller line number
    e.columnNumber;  // caller column number

    // general case
    throw MyError( 'Foo' );

Figure 2.23: Transparent Error extending in ease.js

If ease.js detects that you are extending an Error object or any of its subtypes, it will handle a number of things for you, depending on environment:

  1. Produce a default constructor method (see Constructors) that assigns the error message to the string passed as the first argument;
  2. Sets the error name to the class name;
  3. Provides a stack trace via stack, if supported by the environment, stripping itself from the head of the stack; and
  4. Sets any of fileName, lineNumber, and/or columnNumber when supported by the environment.

If a constructor method is provided in the class definition (see Constructors), then it will be invoked immediately after the error object is initialized by the aforementioned default constructor.8 this.__super in that context refers to the constructor of the supertype (as would be expected), not the default error constructor.

ease.js will automatically detect what features are supported by the current environment, and will only set respective values if the environment itself would normally set them. For example, if ease.js can determine a column number from the stack trace, but the environment does not normally set columnNumber on Error objects, then neither will ease.js; this leads to predictable and consistent behavior.

ease.js makes its best attempt to strip itself from the head of the stack trace. To see why this is important, consider the generally recommended way of creating an Error subtype in ECMAScript:

    function ErrorSubtype( message )
    {
        var err = new Error();

        this.name         = 'ErrorSubtype';
        this.message      = message || 'Error';
        this.stack        = err.stack;
        this.lineNumber   = err.lineNumber;
        this.columnNumber = err.columnNumber;
        this.fileName     = err.fileName;
    }

    ErrorSubtype.prototype             = new Error();
    ErrorSubtype.prototype.constructor = ErrorSubtype;

Figure 2.24: Error subtyping in plain ECMAScript 3

Not only is Figure 2.24 all boilerplate and messy, but it’s not entirely truthful: To get a stack trace, Error is instantiated within the constructor ErrorSubtype; this ensures that the stack trace will actually include the caller. Unfortunately, it also includes the current frame; the topmost frame in the stack trace will be ErrorSubtype itself. To make matters worse, all of lineNumber, columNumber, and fileName (if defined) will be set to the stack frame of our constructor, not the caller.

ease.js will set each of those values to represent the caller. To do so, it parses common stack trace formats. Should it fail, it simply falls back to the default behavior of including itself in the stack frame.

The end result of all of this is—hopefully—concise Error subtypes that actually function as you would expect of an Error, without any boilerplate at all. The Error subtypes created with ease.js can be extended like the built-ins, and may extend any of the built-in error types (e.g. TypeError and SyntaxError).


Footnotes

(8)

The reason that ease.js does not permit overriding the generated constructor is an implementation detail: the generated constructor is not on the supertype, so there is not anything to actually override. Further, the generated constructor provides a sane default behavior that should be implicit in error classes anyway; that behavior can be overridden simply be re-assigning the values that are assigned for you (e.g. name or line number).


Next: , Previous: , Up: Inheritance   [Contents]