Error Handling in JavaScript: Try, Catch, Finally

Introduction
Hello readers,
Welcome to another blog. So far, we have delved into callbacks, promises, and async/await in JavaScript. But without error handling, our code is incomplete. We are all familiar with those annoying errors. So in this blog, I will cover error handling and its methods.
Most people learn error handling like this:
“Put code in try, handle it in catch, and finally will always run.”
Sounds simple… but that’s only 10% of the real story.
Let’s break it properly.
What is an error?
In JavaScript, an error is not just “something went wrong”.
It’s a special object that stops normal execution.
Types of common runtime errors:
ReferenceError → variable not defined
TypeError → wrong operation on a value
SyntaxError → invalid code (caught before execution)
Example:
console.log(user.name); // user is not defined
Core Idea: try-catch is NOT for everything
A common misconception:
“Wrap everything in try-catch.”
Absolutely Wrong.
try...catch only works for:
- Synchronous runtime errors
It does NOT catch:
Syntax errors (before execution)
Async errors (like setTimeout, promises)
Example:
try {
setTimeout(() => {
throw new Error("Boom");
}, 1000);
} catch (e) {
console.log("Caught"); //Won't run
}
Flow of try-catch-finally
Let's see the code first:
try {
Execute this block
} catch (err) {
Runs ONLY if error occurs in try
} finally {
ALWAYS runs (optional)
}
finally runs:
after try (if no error)
after catch (if error occurs)
Even if you return inside try or catch, finally still runs
Example:
function test() {
try {
return "from try";
} finally {
console.log("finally runs");
}
}
console.log(test());
Output:
from try
finally runs
Important Nuance:
1. Finally, it can override everything
This is something many devs don’t know:
function test() {
try {
return "try";
} finally {
return "finally";
}
}
console.log(test());
2. catch is Optional (Yes, Really)
You can use try with only finally:
Example:
try{
console.log("try block")
}finally{
console.log("final block")
}
3. Optional catch parameter (Modern JS)
You don’t always need the error variable:
try {
riskyCode();
} catch {
console.log("Something failed");
}
4. Throwing Custom Errors (Correct Way)
Instead of throwing random values:
throw "error"; // bad practice
Use Error:
throw new Error("Invalid input");
You can also customize:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
try-catch vs Promises (Very Important)
try-catch works with async/await, NOT plain promises.
For example, here, try-catch will not work
try {
fetchData().then(() => {
throw new Error("error");
});
} catch (e) {
console.log("Not caught");
}
Here is the correct working code:
try {
await fetchData();
} catch (e) {
console.log("Caught");
}
Nested try-catch
try {
try {
throw new Error("Inner error");
} catch (e) {
console.log("Handled inner");
}
} catch (e) {
console.log("Outer catch");
}
Once handled, it doesn’t propagate unless rethrown.
Re-throwing Errors
We can also throw errors. Throwing errors can be useful when you want to standardize errors and handle them from a single instance.
try {
risky();
} catch (e) {
console.log("Logging error");
throw e; // rethrow
}
Importance of error handling
Without proper understanding:
You think errors are handled… but they’re not
Async bugs slip through
Production crashes silently
With proper understanding:
You know where errors are catchable
You avoid false confidence
You build robust systems
TLDR;
Think of it as:
try→ “Attempt risky code.”catch→ “Handle only sync failures.”finally→ “Run cleanup no matter what.”
Don't think of it as a sequence.
That's all for now. I hope you gained a clear understanding of the importance of error handling and the methods to manage errors effectively. Please share your feedback, and I'll see you in another in-depth blog soon.
Until then...
Stay consistent & keep grinding.
Peace✌️





