Exception Handling

    Beyond the built in Error class there are a few additional built-in error classes that inherit from Error that the JavaScript runtime can throw:

    Creates an instance representing an error that occurs when a numeric variable or parameter is outside of its valid range.

    1. // Call console with too many arguments
    2. console.log.apply(console, new Array(1000000000)); // RangeError: Invalid array length

    ReferenceError

    Creates an instance representing an error that occurs when de-referencing an invalid reference. e.g.

    1. 'use strict';
    2. console.log(notValidVar); // ReferenceError: notValidVar is not defined

    Creates an instance representing a syntax error that occurs while parsing code that isn’t valid JavaScript.

    1. 1***3; // SyntaxError: Unexpected token *

    TypeError

    Creates an instance representing an error that occurs when a variable or parameter is not of a valid type.

    1. decodeURI('%'); // URIError: URI malformed

    Beginner JavaScript developers sometimes just throw raw strings e.g.

    1. try {
    2. }
    3. catch(e) {
    4. console.log(e);
    5. }

    Don’t do that. The fundamental benefit of objects is that they automatically keep track of where they were built and originated as the stack property.

    Raw strings result in a very painful debugging experience and complicate error analysis from logs.

    It is okay to pass an Error object around. This is conventional in Node.js callback style code which take callbacks with the first argument as an error object.

    1. function myFunction (callback: (e?: Error)) {
    2. doSomethingAsync(function () {
    3. if (somethingWrong) {
    4. callback(new Error('This is my error'))
    5. } else {
    6. callback();
    7. }
    8. });
    9. }

    Exceptions should be exceptional is a common saying in computer science. There are a few reasons why this is true for JavaScript (and TypeScript) as well.

    Unclear where it is thrown

    The next developer cannot know which funtion might throw the error. The person reviewing the code cannot know without reading the code for task1 / task2 and other functions they might call etc.

    You can try to make it graceful with explicit catch around each thing that might throw:

    1. try {
    2. const foo = runTask1();
    3. }
    4. catch(e) {
    5. console.log('Error:', e);
    6. }
    7. const bar = runTask2();
    8. }
    9. console.log('Error:', e);
    10. }

    But now if you need to pass stuff from the first task to the second one the code becomes messy: (notice foo mutation requiring let + explicit need for annotating it because it cannot be inferred from the return of runTask1):

    1. let foo: number; // Notice use of `let` and explicit type annotation
    2. try {
    3. foo = runTask1();
    4. }
    5. catch(e) {
    6. console.log('Error:', e);
    7. }
    8. try {
    9. const bar = runTask2(foo);
    10. }
    11. catch(e) {
    12. console.log('Error:', e);
    13. }

    Not well represented in the type system

    Consider the function:

    1. function validate(value: number) {
    2. }

    Using Error for such cases is a bad idea as it is not represented in the type definition for the validate function (which is ). Instead a better way to create a validate method would be: