Error Handling in JavaScript

Depending on whether a language is pre-compiled or runs Just-In-Time has an effect on what type of error it can cause. TypeScript is pre-compiled to JavaScript, but JavaScript itself runs Just-In-Time, do there are Compile time errors in TypeScript but not in JavaScript. JavaScript only knows runtime errors.

  • SyntaxError (e.g. a missing closing bracket)
    if { var a = "" triggers
    Uncaught SyntaxError: Unexpected token '{'
  • TypeError (variable or parameter is not of a valid type, e.g. misspelling a function name)
    document.getEmelentsById() triggers
    Uncaught TypeError: document.getEmelentsById is not a function)
  • ReferenceError (calling Foo.round() if Foo was never defined triggers a Uncaught ReferenceError: Foo is not defined
  • RangeError, URIError, AggregateError

JavaScript errors are the same as exceptions, but everything in JavaScript evolves around the global Error object.

Throwing errors yourself

Another category of errors are Logic errors, e.g. user entered month as text but in your logic you expected a number.

if(isNaN(month)) {
  // you can also neglect the new-keyword
  let err = new TypeError("Month should be a number", {cause: "my fault"});
  console.log(err.message);
  console.log(err.name);
  console.log(err.stack); // non-standard property
  console.log(err.cause); // not displayed in web console, but can be used internally to write code for logging
  console.log(err.columnNumber);
}

The above example creates an error object, but it does not stop further execution of the code. Only after throwing the error, the error must either be handled in a try…catch block or the JS engine stops the code execution with an Uncaught Error:

try {
  throw new Error("Whoops!");
} catch (e) {
  console.error(`${e.name}: ${e.message}`);
}

Create custom error types

class CustomError extends Error {
  constructor(foo = "bar", ...params) {
    // Pass remaining arguments (including vendor specific ones) to parent constructor
    super(...params);

    // Maintains proper stack trace for where our error was thrown (only available on V8)
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, CustomError);
    }

    this.name = "CustomError";
    // Custom debugging information
    this.foo = foo;
    this.date = new Date();
  }
}

try {
  throw new CustomError("baz", "bazMessage");
} catch (e) {
  console.error(e.name); // CustomError
  console.error(e.foo); // baz
  console.error(e.message); // bazMessage
  console.error(e.stack); // stacktrace
}

Handling a specific error type

You simply check the instanceof the Error:

try {
  foo.bar();
} catch (e) {
  if (e instanceof EvalError) {
    console.error(`${e.name}: ${e.message}`);
  } else if (e instanceof RangeError) {
    console.error(`${e.name}: ${e.message}`);
  }
  // etc.
  else {
    // If none of our cases matched leave the Error unhandled
    throw e;
  }
}

Rethrowing errors

You can catch an error and throw it again with a new error message. A reason why you would want to do that is because it allows you to handle that original error in different ways. The way to do this is to pass the original error as the cause for the new errors.

In this example we rethrow the error if it failed in some way and rethrow another error if it failed in another way.

function doWork() {
  try {
    doFailSomeWay();
  } catch (err) {
    throw new Error("Failed in some way", { cause: err });
  }
  try {
    doFailAnotherWay();
  } catch (err) {
    throw new Error("Failed in another way", { cause: err });
  }
}

try {
  doWork();
} catch (err) {
  switch (err.message) {
    case "Failed in some way":
      handleFailSomeWay(err.cause);
      break;
    case "Failed in another way":
      handleFailAnotherWay(err.cause);
      break;
  }
}

Handling async errors

const getAsyncResult = (willCauseError) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => (willCauseError ? reject(new Error("Oops")) : resolve("Worked")), 300);
    });
}

getAsyncResult(true).catch(err => console.log("We caught it", err));

// or with async...await
async function runMe() {
    try {
        await getAsyncResult(true);
    } catch (err) {
        console.log("We caught it", err);
    }
}

runMe();

About Author

Mathias Bothe To my job profile

I am Mathias from Heidelberg, Germany. I am a passionate IT freelancer with 15+ years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I create Bosycom and initiated several software projects.