Error Handling in JavaScript
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.
- The
try
block wrap your code to check for errors. - The
throw
keyword is used to throw custom errors. - The
catch
block handle the caught errors. You chaincatch
block withtry
block. - The
finally
block of code is always executed regardless of the result. You chainfinally
block withtry
andcatch
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:
- The
finally
block is executed even after error is thrown fromtry
block - 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:
- The code after throwing error in
try
block never reached. - Error is handled gracefully this time by
catch
block. - The
finally
block is executed even after error is thrown fromtry
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