Skip to main content

Command Palette

Search for a command to run...

Error Handling in JavaScript: Try, Catch, Finally

Updated
4 min read
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:

  1. ReferenceError → variable not defined

  2. TypeError → wrong operation on a value

  3. 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
  }
💡
Why? Because the error happens later, outside the try block.

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✌️