Explorer
JavaScript

Error Handling

JavaScript Theory & Concepts

Error Handling in JavaScript

Defensive programming with try/catch/finally, custom error hierarchies, and async failure handling.

📖 The Story & Real-World Analogy

The Acrobat and the Safety Net

"Imagine high-flying trapeze acrobats performing complex aerial stunts in a grand circus tent (executing network queries, parsing JSON, writing files). They do not perform over bare concrete. They suspend an elastic safety net (`try...catch`) underneath. If an acrobat slips (an unhandled error is thrown), the net catches them safely, cushions the fall, and the ringmaster (`finally`) sweeps the floor so the show can continue without injury."

Robust error handling prevents unexpected runtime exceptions from crashing your application, providing graceful degradation and informative debugging telemetry.

⚙️ How It Works Under The Hood (Step-by-Step)
1

The Error Object Anatomy

Standard `Error` instances provide `name` (e.g., TypeError, SyntaxError), `message` (human-readable reason), and `stack` (the Call Stack trace).

2

try...catch...finally Mechanics

Code in `try` executes. If an error is thrown, control transfers immediately to `catch(err)`. The `finally` block executes unconditionally, even if `try` returns or `catch` throws.

3

Building Custom Error Classes

Extend `Error` (`class ValidationError extends Error`) to build typed errors with HTTP status codes and domain-specific metadata.

4

Asynchronous Error Trapping

`try...catch` only catches synchronous errors within its block. To catch asynchronous errors, wrap `await` calls inside `try...catch` or use `.catch()` on promise chains.

💻 Interactive Code Walkthrough

Creating custom domain errors and handling both synchronous and async exceptions:

JAVASCRIPT
// Custom Domain Error Class
class DatabaseError extends Error {
  constructor(message, code) {
    super(message);
    this.name = "DatabaseError";
    this.code = code;
  }
}

// Handling async errors cleanly with try/catch/finally
async function loadUserProfile(userId) {
  let connection = null;
  try {
    connection = { status: "connected" }; // Acquire resource
    if (userId <= 0) {
      throw new DatabaseError("Invalid User ID requested", "ERR_INVALID_ID");
    }
    return { id: userId, name: "Maria" };
  } catch (err) {
    if (err instanceof DatabaseError) {
      console.error(`[${err.code}] Domain Error: ${err.message}`);
    } else {
      console.error("Unexpected failure:", err);
    }
    return null;
  } finally {
    // Guaranteed cleanup regardless of success or error
    if (connection) {
      connection.status = "closed";
      console.log("Connection safely closed in finally block.");
    }
  }
}

loadUserProfile(-1);
Console Output:
CODE
[ERR_INVALID_ID] Domain Error: Invalid User ID requested
Connection safely closed in finally block.
⚠️ Common Pitfalls & Interview Traps
Trap
Silent Catches (Swallowing Errors)

The Risk: Writing `catch (e) {}` with no logging or recovery hides critical bugs, making production issues impossible to debug.

The Fix: Always log caught errors or re-throw them after augmenting with context.

Trap
Catching Async Errors with Synchronous try/catch

The Risk: Wrapping a non-awaited async function or `setTimeout` inside `try...catch` fails to catch exceptions thrown after the current stack frame finishes.

The Fix: Always use `await` inside `try` blocks when calling async functions.

⚡ 30-Second Quick Revision Cheat Sheet (TL;DR)
  • ✓ `try` monitors code; `catch` handles failures; `finally` always runs.
  • ✓ `finally` executes even if `try` contains a `return` statement.
  • ✓ Use `instanceof` to catch specific error subclasses.
  • ✓ Always `await` promises inside `try...catch` blocks.
  • ✓ Global listeners: `window.onerror` in browsers, `process.on("unhandledRejection")` in Node.js.

Finished this lesson?

Mark this chapter complete to update your learning streak and unlock the next lesson.