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
tryblock wrap your code to check for errors. - The
throwkeyword is used to throw custom errors. - The
catchblock handle the caught errors. You chaincatchblock withtryblock. - The
finallyblock of code is always executed regardless of the result. You chainfinallyblock withtryandcatchblock.
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
finallyblock is executed even after error is thrown fromtryblock - Error is not handled gracefully without
catchblock 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
tryblock never reached. - Error is handled gracefully this time by
catchblock. - The
finallyblock is executed even after error is thrown fromtryblock.
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