🌱BeginnerControl Flow

Error Handling

try/catch/finally, throwing errors, and the built-in Error types for writing resilient code.

Overview

Errors are inevitable a network request fails, a user provides invalid input, or a calculation divides by zero. JavaScript provides `try/catch/finally` to handle these situations gracefully instead of crashing the program. The `try` block contains code that might fail. If any statement inside it throws an error, JavaScript immediately jumps to the `catch` block, skipping the rest of `try`. The `catch` block receives the error object, which has a `message` and a `stack` trace. The optional `finally` block always runs after either `try` or `catch`, whether or not an error occurred useful for cleanup like closing connections. You can throw your own errors with `throw new Error("message")` or use specific subtypes like `TypeError`, `RangeError`, and `ReferenceError`. You can also extend `Error` to create custom error classes for your application.

Key Points

  • •`try { ... } catch (err) { ... }` the error object `err` has `err.message` and `err.stack`
  • •`finally { ... }` runs regardless of success or failure; ideal for cleanup code
  • •`throw expression` throws any value, but always throw `Error` objects for good stack traces
  • •`new Error("message")` generic error; subtypes: `TypeError`, `RangeError`, `ReferenceError`, `SyntaxError`
  • •`err instanceof TypeError` check the specific error type in catch
  • •Extend `Error` to create custom error classes: `class ValidationError extends Error {}`
  • •Errors in async code: `try/catch` works with `async/await`; `.catch()` handles Promise rejections

Examples

Basic try/catch/finally

function parseJSON(str) {
  try {
    const result = JSON.parse(str);
    return result;
  } catch (err) {
    console.log("Parse failed:", err.message);
    return null;
  } finally {
    console.log("parseJSON called");   // always runs
  }
}

console.log(parseJSON('{"name":"Alice"}'));  // {name: "Alice"}
console.log(parseJSON("invalid json"));     // Parse failed: ... | null

Throwing and catching specific error types

function divide(a, b) {
  if (typeof a !== "number" || typeof b !== "number") {
    throw new TypeError("Both arguments must be numbers");
  }
  if (b === 0) {
    throw new RangeError("Cannot divide by zero");
  }
  return a / b;
}

try {
  console.log(divide(10, 2));   // 5
  console.log(divide(10, 0));   // throws RangeError
} catch (err) {
  if (err instanceof RangeError) {
    console.log("Math error:", err.message);
  } else if (err instanceof TypeError) {
    console.log("Type error:", err.message);
  } else {
    throw err;   // re-throw unknown errors
  }
}

Custom error class

class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

function validateAge(age) {
  if (typeof age !== "number") {
    throw new ValidationError("age", "Age must be a number");
  }
  if (age < 0 || age > 150) {
    throw new ValidationError("age", `Age ${age} is out of valid range`);
  }
  return true;
}

try {
  validateAge(200);
} catch (err) {
  if (err instanceof ValidationError) {
    console.log(`Validation failed on '${err.field}': ${err.message}`);
  }
}

Common Mistakes

  • Catching errors silently (`catch (err) {}`) always at least log the error so you know something went wrong
  • Putting too much code in `try` keep it focused on the operation that might fail, not the whole function
  • Not re-throwing unknown errors if you only handle specific error types, re-throw others: `else { throw err; }`

Note

Use `finally` for cleanup that must happen regardless of success or failure: closing a file, releasing a lock, hiding a loading spinner. If both `catch` and `finally` are present, `finally` runs after `catch` completes.

Exercises

Write JavaScript code to solve each exercise. Run it and check your output.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

When does the `finally` block run?