Chapter 1 of ?
js 13 min read

JavaScript Mastery — Chapter 16: JS Errors & Exception Handling

Module 3: Errors, Debugging & Quality Chapter 16

JS Errors & Exception Handling

Build resilient, crash-proof JavaScript applications. Master the anatomy of an Error object, standard ECMAScript error types, structured try-catch-finally flow control, custom error hierarchies, error re-throwing, and global unhandled exception monitoring.

16.1

Anatomy of a JavaScript Error & The Call Stack

In JavaScript, an error is an object thrown to signal that an abnormal condition or runtime failure occurred. All built-in error types inherit from the foundational Error.prototype and expose three essential properties:

error.name
Error Type Identifier

A string representing the class of error (e.g. "TypeError", "ReferenceError", or your custom class name).

error.message
Human-Readable Reason

A descriptive explanation detailing what caused the runtime violation.

error.stack
Call Stack Trace

A chronological trace of all active stack frames, function calls, files, and line numbers leading to the error origin.

Architecture Diagram 16.1: Call Stack Unwinding & Error Propagation
EXECUTION CALL STACK 3. parseData() ← THROW ERROR 2. fetchUserProfile() 1. initDashboard() ← [try/catch] Unwinds Stack CATCH CLAUSE INTERCEPTOR • Error captured in memory • Stack unwinding halted • Graceful fallback displayed ✓ Application Continues Running

Code Example: Inspecting an Error Object

try {
  // Deliberately calling a method on null
  const profile = null;
  profile.getUsername(); 
} catch (err) {
  console.log("Error Name:   ", err.name);    // "TypeError"
  console.log("Error Message:", err.message); // "Cannot read properties of null (reading 'getUsername')"
  console.log("Stack Trace:  \n", err.stack);  // Full chronological call stack with line numbers
}
Try it in Playground
16.2

Standard ECMAScript Error Types

The ECMAScript specification defines six core standard Error constructors that inherit directly from Error. Recognizing these types allows you to write precise diagnostic and recovery handlers:

Error Class When It Is Thrown Common Real-World Example
TypeError A value is not of the expected data type or a non-function is invoked. null.toUpperCase() or const x = 5; x();
ReferenceError Attempting to access a variable that is undeclared or in the Temporal Dead Zone (TDZ). console.log(undefinedVar); or accessing let before declaration.
SyntaxError Code that violates JavaScript grammar rules (or invalid JSON in JSON.parse()). JSON.parse('{ bad: json }') or missing bracket.
RangeError A numeric argument is outside its legal mathematical or engine range. new Array(-5), (3.14).toFixed(200), or infinite recursion.
URIError Global URI functions (decodeURI, decodeURIComponent) receive malformed escape sequences. decodeURIComponent('%')
EvalError Historical error associated with eval() (rarely thrown in modern ES6+). Misuse of legacy evaluation contexts.
Can `try...catch` Catch All SyntaxErrors?

No! If a SyntaxError occurs in raw source code (e.g. typing function( {), the JavaScript engine throws it during the Parsing Phase before any line of code runs. Therefore, a try...catch block around bad syntax cannot catch it. However, runtime syntax failures—such as calling JSON.parse("{ bad json }")—can be trapped by try...catch!

16.3

Structured Exception Flow: try, catch, finally

The try...catch...finally construct allows you to intercept runtime errors and prevent your entire application from abruptly crashing.

1. The try Block

Contains the code that might fail. If no error occurs, execution flows through the end of try and skips catch completely.

2. The catch Block

Executes only if an exception occurs inside try. Modern ES2019 allows optional catch binding (catch { ... } without (err)).

3. The finally Block

Guaranteed to execute unconditionally, whether an error was thrown or not, even if the try or catch block executes a return statement!

Code Example: The Unconditional Finally Guarantee

function processTransaction(account, amount) {
  let isLocked = true;
  console.log("1. Account locked for processing.");

  try {
    if (amount > account.balance) {
      throw new RangeError("Insufficient funds: balance is " + account.balance);
    }
    account.balance -= amount;
    console.log("2. Transaction successful. New balance:", account.balance);
    return true; // Notice: returning from inside try!
  } catch (err) {
    console.warn("2. Transaction rejected:", err.message);
    return false;
  } finally {
    // ALWAYS RUNS — perfect for closing database connections, unlocking mutexes, stopping loading spinners
    isLocked = false;
    console.log("3. FINALLY: Account unlocked safely. isLocked =", isLocked);
  }
}

const userAccount = { balance: 250 };
processTransaction(userAccount, 100); // Successfully deducts and unlocks
processTransaction(userAccount, 500); // Traps RangeError, handles error, and unlocks
Try it in Playground
16.4

Throwing & Custom Error Hierarchies

The throw keyword interrupts standard execution and throws an exception. While JavaScript allows you to throw any value (strings, numbers, objects), always throw an instance of Error (or an extended subclass) so that call stack traces and prototype chains are preserved.

Code Example: Building an Enterprise Custom Error Hierarchy

// Base Application Error
class AppError extends Error {
  constructor(message, statusCode = 500, errorCode = "INTERNAL_ERROR") {
    super(message);
    this.name = this.constructor.name;
    this.statusCode = statusCode;
    this.errorCode = errorCode;
    this.timestamp = new Date().toISOString();
    
    // Capture clean V8 stack trace excluding constructor frame
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, this.constructor);
    }
  }
}

// Specialized Validation Error
class ValidationError extends AppError {
  constructor(message, invalidFields = []) {
    super(message, 400, "VALIDATION_FAILED");
    this.invalidFields = invalidFields;
  }
}

// Specialized Authentication Error
class AuthError extends AppError {
  constructor(message = "Authentication required") {
    super(message, 401, "UNAUTHORIZED");
  }
}

// Usage in business logic
function registerUser(userData) {
  if (!userData.email || !userData.email.includes("@")) {
    throw new ValidationError("Invalid email provided", ["email"]);
  }
  if (!userData.token) {
    throw new AuthError("Session expired, please log in again");
  }
  return { success: true, user: userData };
}

try {
  registerUser({ email: "invalid-email" });
} catch (err) {
  if (err instanceof ValidationError) {
    console.error(`[${err.errorCode}] ${err.message} (Fields: ${err.invalidFields.join(", ")})`);
  } else if (err instanceof AuthError) {
    console.error(`[${err.errorCode}] Redirecting to login page...`);
  } else {
    console.error("Unknown system failure:", err);
  }
}
Try it in Playground
16.5

Defensive Patterns & Global Error Handlers

Catching errors is only half the battle. Good architecture requires knowing when to re-throw unexpected errors and how to monitor global unhandled rejections:

The Error Re-Throwing Pattern

Only catch errors that your function knows how to recover from. If an error is of an unexpected type (e.g. an unexpected ReferenceError due to a typo), re-throw it so higher-level handlers or telemetry catch it.

Global Safety Nets

In modern browsers, register window.onerror for uncaught sync errors and unhandledrejection for unhandled Promise rejections to send telemetry to monitoring services (like Sentry).

Code Example: Re-throwing & Global Telemetry

// 1. Re-throwing unknown errors
function loadUserData(jsonStr) {
  try {
    const data = JSON.parse(jsonStr);
    return data.user;
  } catch (err) {
    if (err instanceof SyntaxError) {
      console.warn("Recovering from corrupted cache JSON. Using empty default.");
      return null;
    }
    // Unknown bug: Re-throw to caller!
    throw err;
  }
}

// 2. Global Unhandled Rejection Monitor (Browser)
window.addEventListener("unhandledrejection", (event) => {
  console.error("TELEMETRY ALERT: Unhandled Promise Rejection:", event.reason);
  // Send to server logging API
  // fetch('/api/logs', { method: 'POST', body: JSON.stringify({ error: event.reason.message }) });
});
Try it in Playground
16.6

Interactive Practice Labs

Lab 1: Standard Error Trigger & Stack Inspector
Interactive Sandbox

Trigger different built-in JavaScript error classes and observe how their name, message, and call stack are formatted:

Lab 2: Try-Catch-Finally Execution Flow Tracer

Toggle whether an error is thrown inside the try block and watch the deterministic execution path unfold:

Execution Step Log:
Click "Execute Flow" to run trace...
16.7

Hands-on Challenge & Chapter Quiz

Chapter 16 Coding Challenge: Enterprise Safe JSON Parser

Objective: Write a bulletproof utility function safeJsonParse(jsonString, fallbackValue) that:
1. Traps any SyntaxError or TypeError without crashing.
2. Returns the parsed object if valid, or fallbackValue if parsing fails.
3. Logs a structured warning metric containing the error message and timestamp.

Clean Solution:
function safeJsonParse(jsonString, fallbackValue = null) {
  // Defensive guard against non-string input
  if (typeof jsonString !== "string") {
    console.warn(`[SafeJsonParse] Invalid input type: expected string, received ${typeof jsonString}`);
    return fallbackValue;
  }

  try {
    return JSON.parse(jsonString);
  } catch (err) {
    console.warn(`[SafeJsonParse] Parsing failed at ${new Date().toISOString()}: ${err.message}`);
    return fallbackValue;
  }
}

// Verification:
console.log(safeJsonParse('{"user":"Sanjay","role":"Admin"}')); // { user: "Sanjay", role: "Admin" }
console.log(safeJsonParse('{ corrupt json }', {}));             // Traps SyntaxError and returns {}
console.log(safeJsonParse(12345, []));                          // Traps non-string and returns []
Chapter 16 Knowledge Check

1. What error type is thrown when executing const x = null; x.someMethod();?

A) ReferenceError
B) TypeError
C) NullPointerError
D) SyntaxError

2. What happens if a function executes return "from try" inside try, but also defines a finally block?

A) The finally block is skipped because try returned
B) The finally block still executes before the function completely exits
C) A SyntaxError is thrown
D) The return statement cancels finally execution

3. What is the return value of accessing a variable declared with let before its line of initialization?

A) undefined
B) null
C) It throws a ReferenceError (Temporal Dead Zone)
D) NaN

4. Why is throwing primitive strings (e.g. throw "error") considered bad practice?

A) Strings cannot be caught in catch blocks
B) Primitive strings do not capture a call stack trace or error metadata
C) It causes memory leaks in the V8 garbage collector
D) Strings are automatically converted to numbers

5. Which browser event intercepts unhandled Promise rejections globally?

A) window.onpromiseerror
B) window.addEventListener('unhandledrejection')
C) document.onasyncerror
D) Promise.catchAll()
Done with this chapter?
Mark it complete to track your progress and unlock your certificate.
Next Up
—

Learner Reviews

Write a Review
Share your experience to help other learners.
Your Rating *
★ ★ ★ ★ ★