try catch JavaScript – How to handle errors
In this tutorial, we will learn how to handle errors in JavaScript using try catch.
What is Error Handling?
Let’s understand the behavior of a typical error.
Let’s say we have a function (Function-1
) that calls another function (Function-2
) which further calls another function (Function-3
). Now, if there is an error then, first the execution will stop at Function-3
itself.
Now, there are two ways errors are usually handled:
- On Error Continue: In this case, the error will be swallowed. Basically, it will appear to the caller (in the above example
Function-2
) as if no error occurred. - On Error Propagates: This is the default behavior. In this case, the error will be propagated to the caller i.e
Function-2
. The caller will then handle the error or propagate it further (in the above exampleFunction-1
).
We will see both of these implementations in the following sections.
NOTE: Errors and exceptions are syntactically synonymous in JavaScript.
Error handling is all about gracefully handling the error and making sure that the application does not crash.
For example, if a user tries to access a file that does not exist, we can handle the error and show a message to the user that the file does not exist. You can check it out yourself by visiting codedamn.com/abc. There is no endpoint /abc
in the codedamn website. So, when you request that particular endpoint, the codedamn server instead of getting crashed returns a 404 error
. This is how error handling is done.
Exception Handling statements in JS
In JS we have two statements related to exceptions/errors are:
throw
statementtry...catch
statement
throw statement
We use the throw
to generate errors. The syntax is:
throw expression
Code language: JavaScript (javascript)
We can throw anything in JS. It can be a string, number, boolean, object, etc. But it is recommended to throw meaningful errors which has a name and a message (an error code is also helpful). This makes error handling easier because we can detect the kind of error and its cause.
In JS, we can use some predefined error objects like Error
, SyntaxError
, TypeError
, etc. They have their own constructors and properties. You can check out the MDN documentation for more details.
throw new Error("Something went wrong");
Code language: JavaScript (javascript)
Here we are throwing an Error
with the message “Something went wrong”.
When we also create our own error objects. The best practice is to create a class that extends the Error
class. That way it would automatically include the stack trace in the exception response. This is very useful for diagnosing issues later down the line.
class MyError extends Error {
constructor(message) {
super(message);
this.name = "MyError";
}
}
throw new MyError("This is a custom error")
Code language: JavaScript (javascript)
Here we are throwing an MyError
with the message “This is a custom error”.
This is all about throwing errors in JS. Now let’s see how to handle them.
try...catch statement
Inside the try
block we specify the statements to try. While the block executes if any error is generated the block then captures it. The syntax goes like this:
try {
// statements to try
} catch (e) {
// statements to handle any exceptions
}
Code language: JavaScript (javascript)
One thing to note is that the control will shift immediately to the catch
block if an error is generated. The rest of the statements in the try
block will not be executed.
try {
console.log("This is a try block");
throw new Error("This is an error");
console.log("This will not be executed");
} catch (e) {
console.log("This is a catch block");
}
Code language: JavaScript (javascript)
If no exception is thrown in the try
block, the catch
block will not be executed.
try {
console.log("This is a try block");
console.log("No error is thrown in the try block");
} catch (e) {
console.log("Catch block will not be executed");
}
Code language: JavaScript (javascript)
We already saw that JS generates error objects which have all the information about the error. We can access that information using the argument of the catch
block.
try {
throw new Error("This is an error");
} catch (err) {
console.log(err.name);
console.log(err.message);
console.log(err.stack);
}
Code language: JavaScript (javascript)
The output will be:
Error
This is an error
Error: This is an error
at Object.<anonymous>
....
Code language: Bash (bash)
The Error
object has the following properties:
name
: The name of the error is determined by the constructor.message
: The error message. This is the message that we pass to theError
constructor.stack
: The stack trace of the error. It shows the function call stack when the error was generated.
There are many other properties in the Error
object. You can check them out in the MDN documentation.
finally block
There is another clause that we can add to the try...catch
block. As the name suggests it will always be executed. It is used to execute statements that should be executed regardless of whether an exception is thrown or not. The syntax of try...catch...finally
is:
try {
console.log("This is a try block");
throw new Error("This is an error");
console.log("This will not be executed");
} catch (e) {
console.log("This is a catch block");
} finally {
console.log("This is a finally block");
}
Code language: JavaScript (javascript)
The output will be:
This is a try block
This is a catch block
This is a finally block
Code language: Bash (bash)
To better understand the try
, control
and finally
blocks, take a look at this flowchart.
So, JS starts executing the code inside the try
block.
- If error occurs
- the control will shift to the
catch
block. - then the control will shift to the
finally
block.
- the control will shift to the
- If no error occurs
- it will ignore the catch block
- the control will shift to the
finally
block and execute the statements inside it.
Implementations
Let’s try to put together all the knowledge we have so far and implement the example we discussed in the very beginning.
We will have 3 functions:
- Function-1
- Function-2
- Function-3
Function-1
will call Function-2
and Function-2
will call Function-3
. Function-3
will throw an error. We will handle the error in Function-2
and Function-1
and compare the result.
So the code would look like this:
No error handling
function f1() {
console.log("Function 1 called...");
f2();
console.log("After function 2 was called...");
}
function f2() {
console.log("Function 2 called...");
f3();
console.log("After function 3 was called...");
}
function f3() {
console.log("Function 3 called...");
throw new MyError("Error thrown from function 3", 10);
}
f1();
Code language: JavaScript (javascript)
The output is:
$ node index.js
Function 1 called...
Function 2 called...
Function 3 called...
/home/damner/code/index.js:15
throw new Error("Error thrown from function 3", 10);
^
Error: Error thrown from function 3
at f3 (/home/damner/code/index.js:15:9)
at f2 (/home/damner/code/index.js:9:3)
at f1 (/home/damner/code/index.js:3:3)
at Object.<anonymous> (/home/damner/code/index.js:18:1)
Code language: JavaScript (javascript)
So:
- Function 1 called Function 2, which called Function 3.
- When Function 3 threw an error since there was no error handler at any of the callers the error was thrown to the global scope.
- Also, note none of the
console.log
were executed after the function calls. This is because execution stops when an error occurs.
This is how the flow looks:
Let’s see how we can handle the error.
Error Handler at Function-2
function f1() {
console.log("Function 1 called...");
f2();
console.log("After function 2 was called...");
}
function f2() {
console.log("Function 2 called...");
try {
f3();
} catch (err) {
console.log("Error caught at Function 2");
console.log("Error Name: ", err.name);
console.log("Error Message: ", err.message);
console.log("Error Stack Trace: ", err.stack);
}
console.log("After function 3 was called...");
}
function f3() {
console.log("Function 3 called...");
throw new Error("Error thrown from function 3", 10);
}
f1();
Code language: JavaScript (javascript)
So, here we add a try...catch
block at Function 2 and call the f3
function inside it. If an error occurs, the control will shift to the catch
block and the error will be handled there.
Let’s look at the output:
$ node index.js
Function 1 called...
Function 2 called...
Function 3 called...
Error caught at Function 2
Error Name: Error
Error Message: Error thrown from function 3
Error Stack Trace: Error: Error thrown from function 3
at f3 (/home/damner/code/index.js:23:9)
at f2 (/home/damner/code/index.js:10:7)
at f1 (/home/damner/code/index.js:3:3)
at Object.<anonymous> (/home/damner/code/index.js:26:1)
at Module._compile (internal/modules/cjs/loader.js:1085:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
at Module.load (internal/modules/cjs/loader.js:950:32)
at Function.Module._load (internal/modules/cjs/loader.js:790:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:75:12)
at internal/main/run_main_module.js:17:47
After function 3 was called... <------ Console Logs after the function call
After function 2 was called...
Code language: Bash (bash)
We can see the message “Error caught at Function 2”. It was logged in the catch
block. We have logged other information about the error as well like name
, message
and stack
.
Interestingly, we can see that the console.log
statements after the function calls were executed. This is because the error was handled in the catch
block and execution continued.
The flow for this particular scenario will be:
In this case, to Function-1
everything is okay. It doesn’t know that an error occurred in Function-3
. All the lines of code after the function call were executed like they would have been executed if there was no error.
Error Handler at Function-1
Sometimes we don’t want to handle the error at the immediate caller. Although this isn’t a good practice, for the sake of understanding, let’s see how we can handle the error at the caller of the caller.
So the new code would look like this:
function f1() {
console.log("Function 1 called...");
try {
f2();
} catch (err) {
console.log("Error caught at Function 2");
console.log("Error Name: ", err.name);
console.log("Error Message: ", err.message);
console.log("Error Stack Trace: ", err.stack);
}
console.log("After function 2 was called...");
}
function f2() {
console.log("Function 2 called...");
f3();
console.log("After function 3 was called...");
}
function f3() {
console.log("Function 3 called...");
throw new Error("Error thrown from function 3", 10);
}
f1();
Code language: JavaScript (javascript)
Running this we would see:
Function 1 called...
Function 2 called...
Function 3 called...
Error caught at Function 2
Error Name: Error
Error Message: Error thrown from function 3
Error Stack Trace: Error: Error thrown from function 3
at f3 (/home/damner/code/index.js:22:9)
at f2 (/home/damner/code/index.js:16:3)
at f1 (/home/damner/code/index.js:4:5)
at Object.<anonymous> (/home/damner/code/index.js:25:1)
at Module._compile (internal/modules/cjs/loader.js:1085:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
at Module.load (internal/modules/cjs/loader.js:950:32)
at Function.Module._load (internal/modules/cjs/loader.js:790:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:75:12)
at internal/main/run_main_module.js:17:47
After function 2 was called...
Code language: Bash (bash)
Interesting thing to note here. The console.log
statements after the f3()
call in function 2 didn’t get executed. This is because there was no error handler. So, function 2 had to stop execution and propagate the error to the caller.
Whereas the console.log
statements after the f2()
call in function 1 was executed as we can see in the last line of the output. This is because the error was handled by function 1.
This is what the flow of execution looks like:
Handling Multiple Errors
We can have different types of errors in the try
block. We might want to handle them differently. For example, we might want to handle a TypeError
differently than a ReferenceError
.
Let’s see how we can do that.
function f1() {
try {
// try to access a variable that doesn't exist (ReferenceError)
console.log("Value of a: ", a);
} catch (err) {
if (err instanceof TypeError) {
console.log("TypeError caught at Function 1");
} else if (err instanceof ReferenceError) {
console.log("ReferenceError caught at Function 1");
} else {
console.log("Error caught at Function 1");
}
console.log("Error Name: ", err.name);
console.log("Error Message: ", err.message);
console.log("Error Stack Trace: ", err.stack);
}
}
f1();
Code language: JavaScript (javascript)
We can perform checks on what kind of error we are getting. We can use the instanceof
operator to check the type of error. We can further use other properties of the error object to perform checks like error code.
The output of the above code will be:
ReferenceError caught at Function 1
Error Name: ReferenceError
Error Message: a is not defined
Error Stack Trace: ReferenceError: a is not defined
at f1 (/home/damner/code/index.js:4:33)
at Object.<anonymous> (/home/damner/code/index.js:19:1)
at Module._compile (internal/modules/cjs/loader.js:1085:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
at Module.load (internal/modules/cjs/loader.js:950:32)
at Function.Module._load (internal/modules/cjs/loader.js:790:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:75:12)
at internal/main/run_main_module.js:17:47
Code language: Bash (bash)
Here we can see that the error was caught in the catch
block and the error was of type ReferenceError
. So, we logged the message accordingly.
Important Points
try...catch only work for runtime errors
For a try...catch
block to work, the error must occur at runtime. If the error occurs at compile time, the try...catch
block won’t work. In other words, the JS code has to be syntactically correct for the try...catch
block to work.
try {
let a = ;
} catch (err) {
console.log("Error Found");
}
Code language: JavaScript (javascript)
The above code will throw an error at compile time. So, the try...catch
block won’t work.
try...catch doesn’t work for async code
If the error is thrown from a function that is called asynchronously, the try...catch
block won’t work. This is because the error is thrown from a different thread. The try...catch
block is only for synchronous code.
function f1() {
try {
setTimeout(function(){
throw new Error("Error inside setTimeout");
}, 1000);
} catch (err) {
console.log("Error caught at Function 1");
}
}
f1();
Code language: JavaScript (javascript)
Here we have set a timeout of 1 second. Inside the timeout function, we are throwing an error. So the error won’t be thrown immediately. It will be thrown after 1 second.
By the time the Error is thrown, the try...catch
block has already exited. So, the error is not caught.
Conclusion
In this article, we learned about the try...catch
block in JavaScript. We saw how we can use it to handle errors in our code. We also learned about the finally
clause.
You can read more articles on my blog arnabsen.dev
Sharing is caring
Did you like what Arnab Sen wrote? Thank them for their work by sharing it on social media.
No comments so far
Curious about this topic? Continue your journey with these coding courses: