Error Handling in JavaScript Error Handling in JavaScript

Page content

In this tutorial, we’ll learn error handling using try, catch, finally and throw statements. We’ll also learn about built-in JavaScript error objects (Error, SyntaxError, ReferenceError, etc.) and how to define custom errors.

Using try..catch..finally..throw

We use try, catch, finally and throw keywords in error handling in JavaScript.

  1. The try block wrap your code to check for errors.
  2. The throw keyword is used to throw custom errors.
  3. The catch block handle the caught errors. You chain catch block with try block.
  4. The finally block of code is always executed regardless of the result. You chain finally block with try and catch block.

try

Every try block must be chained with at least one of the catch or finally block otherwise SyntaxError will be thrown.

Let’s use try block alone to verify:

try {
  throw new Error('Error while executing the code');
}
ⓧ Uncaught SyntaxError: Missing catch or finally after try

try..catch

It is recommended to use try with catch block which handles the error gracefully thrown by try block.

try {
  throw new Error('Error while executing the code');
} catch (err) {
  console.error(err.message);
}
ⓧ Error while executing the code

try..catch with invalid code

The try..catch cannot catch the exception of invalid JavaScript code, for example the below code in try block is syntactically wrong and cannot be caught by catch block.

try {
  ~!$%^&*
} catch(err) {
  console.log("code execution will never reach here");
}
ⓧ Uncaught SyntaxError: Invalid or unexpected token

try..catch with asynchronous code

Similarly try..catch cannot catch the exception thrown inside asynchronous code which will be executed later such as setTimeout

try {
  setTimeout(function() {
    noSuchVariable;   // undefined variable
  }, 1000);
} catch (err) {
  console.log("code execution will never reach here");
}

Uncaught ReferenceError will be thrown after 1s

ⓧ Uncaught ReferenceError: noSuchVariable is not definedn

We should use try..catch inside asynchronous code to handle the error gracefully in this way

setTimeout(function() {
  try {
    noSuchVariable;
  } catch(err) {
    console.log("error is caught here!");
  }
}, 1000);

Nested try..catch

We can also use nested try and catch blocks and throw an error upwards like this:

try {
  try {
    throw new Error('Error while executing the inner code');
  } catch (err) {
    throw err;
  }
} catch (err) {
  console.log("Error caught by outer block:");
  console.error(err.message);
}
Error caught by outer block:
➤ ⓧ Error while executing the code

try..finally

It is not recommended to use try with finally (without using catch block in between). Let’s see what happens:

try {
  throw new Error('Error while executing the code');
} finally {
  console.log('finally');
}
finally
➤ ⓧ Uncaught Error: Error while executing the code

We should note two things here:

  1. The finally block is executed even after error is thrown from try block
  2. Error is not handled gracefully without catch block resulting in Uncaught Error

try..catch..finally

It is recommended to use try with catch block and optional finally block.

try {
  console.log("Start of try block");
  throw new Error('Error while executing the code');
  console.log("End of try block -- never reached");
} catch (err) {
  console.error(err.message);
} finally {
  console.log('Finally block always run');
}
console.log("Code execution outside try-catch-finally block continue..");
Start of try block
➤ ⓧ Error while executing the code
Finally block always run
Code execution outside try-catch-finally block continue..

We should note two things here as well:

  1. The code after throwing error in try block never reached.
  2. Error is handled gracefully this time by catch block.
  3. The finally block is executed even after error is thrown from try block.

The finally block is generally used for cleaning up resources or closing the streams such as below:

try {
  openFile(file);
  readFile(file);
} catch (err) {
  console.error(err.message);
} finally {
  closeFile(file);
}

throw

The throw statement is used to throw an exception.

throw <expression>
// throw primitives and functions
throw "Error404";
throw 42;
throw true;
throw {toString: function() { return "I'm an object!"; } };

// throw error object
throw new Error('Error while executing the code');
throw new SyntaxError('Something is wrong with the syntax');
throw new ReferenceError('Oops..Wrong reference');

// throw custom error object
function ValidationError(message) {
  this.message = message;
  this.name = 'ValidationError';
}
throw new ValidationError('Value too high');

Error handling in asynchronous Code

It is recommended to use Promises and async await for asynchronous code (API calls) as they provide support for error handling.

then..catch with Promises

You can chain multiple Promises using then() along with catch() to handle errors of individual promise in the chain like this:

Promise.resolve(1)
  .then(res => {
      console.log(res);  // prints '1'

      throw new Error('something went wrong');  // throw error

      return Promise.resolve(2);  // code will not reach here
  })
  .then(res => {
      // code will not reach since promise not resolved in prev block here due to error
      console.log(res);    
  })
  .catch(err => {
      console.error(err.message);  // prints 'something went wrong'
      return Promise.resolve(3);
  })
  .then(res => {
      console.log(res);  // prints '3'
  })
  .catch(err => {
      // code will not reach since promise resolved in prev block
      console.error(err);
  })

Let’s look at the more practical example where we call an API using fetch which returns a promise object. We handle the API failure gracefully using catch block.

function handleErrors(response) {
    if (!response.ok) {
        throw Error(response.statusText);
    }
    return response;
}

fetch("http://httpstat.us/500")
    .then(handleErrors)
    .then(response => console.log("ok"))
    .catch(error => console.log("Caught", error));
Caught Error: Internal Server Error
    at handleErrors (<anonymous>:3:15)

try..catch with async await

It is very easy to handle errors using try..catch when handling asynchronous with async await like this:

(async function() {
    try {
        await fetch("http://httpstat.us/500");
    } catch (err) {
        console.error(err.message);
    }
})();

Let’s look at the same example where we call an API using fetch which returns a promise object. We handle the API failure gracefully using try..catch block.

function handleErrors(response) {
    if (!response.ok) {
        throw Error(response.statusText);
    }
}

(async function() {
    try {
      let response = await fetch("http://httpstat.us/500");
      handleErrors(response);
      let data = await response.json();
      return data;
    } catch (error) {
        console.log("Caught", error)
    }
})();
Caught Error: Internal Server Error
    at handleErrors (<anonymous>:3:15)
    at <anonymous>:11:7

Built-In JavaScript Errors

Error

JavaScript has built-in Error Object which is generally thrown by try block and caught in catch block.

Error Object consist of following properties:

  • name: is the name of the error for e.g. “Error”, “SyntaxError”, “ReferenceError” etc.
  • message: is the message about error details
  • stack: is the stack trace of the error used for debugging purpose.

Let’s create an Error Object and look at its name and message property:

const err = new Error('Error while executing the code');

console.log("name:", err.name);
console.log("message:", err.message);
console.log("stack:", err.stack);
name: Error
message: Error while executing the code
stack: Error: Error while executing the code
    at <anonymous>:1:13

JavaScript has following built-in errors which are inherited from the Error Object:

EvalError

The EvalError indicates an error regarding the global eval() function. This exception is not thrown by JavaScript anymore and it exist for backward compatibility.

RangeError

The RangeError is thrown when a value out of range.

➤ [].length = -1
ⓧ Uncaught RangeError: Invalid array length

ReferenceError

The ReferenceError is thrown when a variable is referenced which does not exist.

➤ x = x + 1;
ⓧ Uncaught ReferenceError: x is not defined

SyntaxError

The SyntaxError is thrown when you have used any wrong syntax in JavaScript code.

➤ function() { return 'Hi!' }
ⓧ Uncaught SyntaxError: Function statements require a function name

➤ 1 = 1
ⓧ Uncaught SyntaxError: Invalid left-hand side in assignment

➤ JSON.parse("{ x }");
ⓧ Uncaught SyntaxError: Unexpected token x in JSON at position 2

TypeError

The TypeError is thrown when the value is not of the expected type.

➤ 1();
ⓧ Uncaught TypeError: 1 is not a function

➤ null.name;
ⓧ Uncaught TypeError: Cannot read property 'name' of null

URIError

The URIError is thrown when global URI handling function was used in a wrong way.

➤ decodeURI("%%%");
ⓧ Uncaught URIError: URI malformed

Define and throw Custom Error

We can also define our custom error in this way:

class CustomError extends Error {
  constructor(message) {
    super(message);
    this.name = "CustomError";
  } 
};

const err = new CustomError('Custom error while executing the code');

console.log("name:", err.name);
console.log("message:", err.message);
name: CustomError
message: Custom error while executing the code

We can further enhance our CustomError object to include error code as well:

class CustomError extends Error {
  constructor(message, code) {
    super(message);
    this.name = "CustomError";
    this.code = code;
  } 
};

const err = new CustomError('Custom error while executing the code', "ERROR_CODE");

console.log("name:", err.name);
console.log("message:", err.message);
console.log("code:", err.code);
name: CustomError
message: Custom error while executing the code
code: ERROR_CODE

Let’s use this in try..catch block:

try{
  try {
    null.name;
  }catch(err){
    throw new CustomError(err.message, err.name);  //message, code
  }
}catch(err){
  console.log(err.name, err.code, err.message);
}
CustomError TypeError Cannot read property 'name' of null