// NOTE: If you browser supports ES6, you can simply "extends Error". But, not all // browsers support ES6 class definitions yet. interface ErrorSubclass extends Error { // Here, we are defining an interface - ErrorSubclass - that shares the same name as // our class. In TypeScript, this allows us to define aspects of the Class that we // don't actually have to implement in the class definition. In this case, we're // using the interface to tell TypeScript that ErrorSubclass extends Error even // though our class definition doesn't use "extends". We're doing this here so that // TypeScript doesn't try to implement the extends on the class itself - we're going // to do it explicitly with the .prototype. } // I am a "hacky" class that helps extend the core Error object in TypeScript. This // class uses a combination of TypeScript and old-school JavaScript configurations. class ErrorSubclass { public name: string; public message: string; public stack: string; // I initialize the Error subclass hack / intermediary class. constructor( message: string ) { this.name = "ErrorSubclass"; this.message = message; // CAUTION: This doesn't appear to work in IE, but does work in Edge. In // IE, it shows up as undefined. this.stack = ( new Error( message ) ).stack; } } // CAUTION: Instead of using the "extends" on the Class, we're going to explicitly // define the prototype as extending the Error object. ErrorSubclass.prototype = <any>Object.create( Error.prototype ); // ----------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------- // interface AppErrorOptions { message: string; detail?: string; extendedInfo?: string; code?: string; rootError?: any; } class AppError extends ErrorSubclass { public name: string; public detail: string; public extendedInfo: string; public code: string; public rootError: any; // I initialize the AppError with the given options. constructor( options: AppErrorOptions ) { super( options.message ); this.name = "AppError"; this.detail = ( options.detail || "" ); this.extendedInfo = ( options.extendedInfo || "" ); this.code = ( options.code || "" ); this.rootError = ( options.rootError || null ); } // --- // PUBLIC METHODS. // --- // I am here to ensure that public methods can work with a Class that extends an // object that extends Error. public testPublicMethod() : string { return( "Public method exists on the Error sub-class." ); } } // ----------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------- // try { throw( new Error( "Boom!" ) ); // NOTE: The value in a catch() block has an implicit ANY type and cannot be given a more // specific type annotation. This makes sense because there's no way to consistently know // the root cause of an error at compile time. } catch ( error ) { logError( <Error>error ); try { // Wrap the caught error in a custom AppError. throw( new AppError({ message: "Something went horribly wrong!", detail: "You crossed the streams!", code: "gb", rootError: error }) ); } catch ( nestedError ) { // NOTE: Using TypeScript to cast the "any" type in the catch-block to an // explicit AppError. This way, we can see if the AppError correctly fits into // the "Error" type expected by the logError() function. logError( <AppError>nestedError ); // Test to make sure the public method works (ie, that inheritance worked // without screwing up the concrete class). console.info( nestedError.testPublicMethod() ); } } // ----------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------- // // I log the given error object to the console. function logError( error: Error ) : void { group( "Log Error" ); console.log( "Error instance: ", ( error instanceof Error ) ); console.log( "AppError instance: ", ( error instanceof AppError ) ); console.log( "Message: " + error.message ); // If we're dealing with an AppError, we can output additional properties. // -- // NOTE: In TypeScript, this IF-expression is known as a "Type Guard", and will // tell TypeScript to treat the "error" value as an instance of "AppError" for the // following block, which is great because we will get the extra type-protection // for the values on the AppError class. if ( error instanceof AppError ) { console.log( "Detail: " + error.detail ); console.log( "Extended Info: " + error.extendedInfo ); console.log( "Code: " + error.code ); console.log( "Root Error: " + error.rootError.message ); } // NOTE: The .stack property is only populated in IE 10+ AND, even then, only when // an error instance is thrown. Also, it looks like this might not get populated on // sub-classes in console.log( error.stack ); groupEnd(); } // ----------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------- // // I safely define a "console.group()" method, which is not available in some IE. function group( name: string ) : void { console.group ? console.group( name ) : console.log( "- - - [ " + name + " ] - - - - - - - - - - - - - -" ) ; } // I safely define a "console.groupEnd()" method, which is not available in some IE. function groupEnd() : void { console.groupEnd ? console.groupEnd() : console.log( "- - - [ END ] - - - - - - - - - - - - - -" ) ; }