Better Errors Through JavaScript Inheritance

O

ne of the more frustrating things in JavaScript has always been how Errors are handled. They are difficult to check for, harder to extend and often times have cryptic messages providing little insight into what has gone wrong.

Uncaught TypeError: undefined is not a function  

Everyone has seen this at one time or another. It isn't very helpful, and these types of errors are actually difficult to account for in JavaScript applications. The only error handling in JavaScript that the language allows is generic try/catch, where you can catch an error, but trying to recover from specific errors is difficult to do. Large in part the the fact that you can throw anything, not just errors. You can throw any Object, and it is legal syntax. While JavaScript does have the instanceof operator that would allow you the check error types, it checks the prototype chain, so it can have some odd behaviors.

Inheriting from the Error Object has some unusual behavior

Using the instance operator isn't quite what we are looking for. If the object you are comparing against is anywhere in the prototype chain, it will return true.

MyError instanceof MyError // true  
MyError instanceof Error // true  

This is more of what you would like to do in JavaScript. Knowing that certain operations can produce specific error types that can be accounted for. Unfortunately, trying to subclass Error also has some unusual behavior.

function Exception(name, message){  
    Error.call( this, name, message );
};
Exception.prototype = Object.create( Error.prototype );

var e = new Exception("BrokeException","This is broke");  
throw e;

// Error

Neither the name or message were captured here. More Over, it doesn't have a stacktrace. This is worse that just throwing an instance of Error or a String for that matter. We can do better than this, but it will take a little work.

Exception Class

var util = require('util')

function quickmerge( ){  
    var i = 1, target, obj;

    target = arguments[0];

    while( obj = arguments[i++]){
        console.log( target )
        for( var key in obj ){
            target[key] = obj[key]
        }
    }
    return target;
};

function Exception( options){  
    var tmp // temporary error object
    this.options = quickmerge({
        name:'Exception'
        ,message:''
        ,code:1000
        ,type:"base_exception"
    },options || {}  );


    // force this thing to really be an error
    tmp        = Error.call( this, this.options.message );
    tmp.name   = this.name;

    this.stack = tmp.stack;

    // cleanup
    tmp        = undefined;
};

Exception.prototype = Object.create( Error.prototype );

Object.defineProperties( Exception.prototype, {  
    name:{
        get: function(){
            console.log('name lookup')
            return this.options.name;
        }
    }
    ,code:{
        get: function( ){
            return this.options.code;
        }
    }
    ,type:{
        get: function( ){
            return this.options.type;
        }
    }
    ,message:{
        get:function(){
            return this.options.message;
        }
    }
})

Exception.prototype.toString = function( ){  
    return util.format( '[%s] %s', this.name, this.message )
};

Exception.prototype.toJSON = function(){  
    return{
        name: this.name
      , message: this.message
      , code: this.code
      , stack: this.stack
      , type: this.type
    }
};

var e = new Exception({name:"BetterException",message:"This is better"})  
throw e;

// output

BetterException: This is better  
    at new Error (<anonymous>)
    at Error.Exception (repl:10:20)
    at repl:1:9
    at REPLServer.self.eval (repl.js:110:21)
    at repl.js:249:20
    at REPLServer.self.eval (repl.js:122:7)
    at Interface.<anonymous> (repl.js:239:12)
    at Interface.EventEmitter.emit (events.js:95:17)
    at Interface._onLine (readline.js:202:10)
    at Interface._line (readline.js:531:8)

This is quite a bit better. Not only is there a stack trace, but it has the right name, and message. Additionally, we have given our error a bit more meta data so developers can figure out what they are dealing with - a code and an internal type. Not perfect, but certainly a step in the right direction in terms of being able to deal with errors in a more logical and structured way.

oop javascript errors inheritance