Error Handling

Course: JavaScript Intermediate

Lesson 17: Error Handling

Level

Intermediate

Estimated Reading Time

10–15 minutes

Goal

By the end of this lesson, the learner will be able to:

  • Use try/catch/finally to handle runtime errors gracefully
  • Access properties of the Error object such as message and name
  • Throw custom errors with meaningful messages using throw new Error()

Frontend Development Context

Frontend applications interact with user input, external APIs, and browser APIs — all of which can fail in unexpected ways. Without error handling, a single runtime error can crash the entire application or leave the user staring at a blank screen. Proper error handling means catching problems early, showing the user useful feedback, and keeping the application running.

Error handling also makes debugging faster. When you catch an error and log its message, you know exactly what went wrong and where. When you throw custom errors with descriptive messages, the developer (or future you) gets a clear explanation instead of a cryptic stack trace.

Explanation

A try/catch block lets you run code that might throw an error and handle the error gracefully if it does. Code inside try runs normally. If any line throws an error, execution jumps immediately to the catch block, skipping the rest of the try block. The catch block receives an Error object with details about what happened.

The Error object has two key properties: message (a human-readable description of the error) and name (the type of error, such as "TypeError", "ReferenceError", or "Error"). You can also access the stack property for a call stack trace, though its format varies across browsers.

The finally block runs after the try/catch completes, regardless of whether an error occurred. This is the right place for cleanup code: hiding loading spinners, closing connections, or resetting UI state that should always be restored.

You can throw your own errors using throw new Error("message"). This creates a standard Error object with a custom message. You can also throw custom error types by extending the built-in Error class, giving you more specific error categories.

Error handling should be targeted — wrap only the code that can actually fail, not your entire application. Catching too broadly can hide bugs. Always log errors or surface them to the user in some way.

Code Example

This example validates a user input, throws a custom error if invalid, and handles errors from a simulated async operation.

// Custom validation function that throws descriptive errors
function validateAge(age) {
  if (typeof age !== "number") {
    throw new TypeError(`Expected a number, got ${typeof age}`);
  }
  if (age < 0 || age > 120) {
    throw new RangeError(`Age ${age} is out of valid range (0–120)`);
  }
  return true;
}

// Using try/catch to handle the thrown error
function processAge(input) {
  try {
    validateAge(input);
    console.log(`Age ${input} is valid`);
  } catch (error) {
    console.error(`${error.name}: ${error.message}`);
  } finally {
    console.log("Validation attempt complete");
  }
}

processAge(25);    // "Age 25 is valid" | "Validation attempt complete"
processAge("25");  // "TypeError: Expected a number, got string" | "Validation attempt complete"
processAge(200);   // "RangeError: Age 200 is out of valid range (0–120)" | "..."

// Handling JSON parse errors
function parseConfig(jsonString) {
  try {
    const config = JSON.parse(jsonString);
    return config;
  } catch (error) {
    console.error("Failed to parse config:", error.message);
    return {}; // return safe default
  }
}

console.log(parseConfig('{"theme":"dark"}')); // { theme: "dark" }
console.log(parseConfig("not json"));         // {} — error caught, default returned

Code Explanation

validateAge uses throw new TypeError(...) and throw new RangeError(...) to throw specific error types. Using the appropriate built-in error type (TypeError for wrong type, RangeError for out-of-bounds values) makes error messages more informative and allows callers to catch specific error types if needed.

processAge wraps validateAge in a try/catch. If validateAge throws, execution jumps to catch(error). The error object's name property is the error type and message is the description we set. Logging error.name + ": " + error.message gives a clear, formatted error report.

finally runs after either the try block succeeds or the catch block handles an error. It always runs — here it logs "Validation attempt complete" in all three cases.

parseConfig shows error handling for a common real-world task: parsing JSON from an unknown source. If the string is invalid JSON, JSON.parse throws a SyntaxError. The catch block returns an empty object as a safe fallback, preventing the error from crashing the calling code.

Pattern Highlights

Positive Pattern: Throw errors with descriptive messages that explain what went wrong and what was expected. throw new Error("userId is required") is far more helpful than a silent failure or a generic "Error" message.

⚠️ Neutral Note: finally always runs — even if the try block has a return statement. Use this for cleanup that must always happen, but be aware that a return in finally overrides the return in try.

Negative Pattern: Do not use an empty catch block that silently swallows errors. catch (e) {} hides bugs and makes them nearly impossible to diagnose. Always at minimum log the error.

Common Mistakes

Mistake 1: Using an empty catch block

Catching errors but doing nothing with them.

try {
  JSON.parse(badInput);
} catch (e) {
  // silent — the error disappears
}

This hides the error completely. At minimum, log it: console.error(e.message). In user-facing code, also update the UI to show that something went wrong.

Mistake 2: Wrapping too much code in a single try block

Catching errors from many operations in one block, making it hard to know which line failed.

try {
  const data = JSON.parse(raw);
  const user = data.users[0];
  renderProfile(user);
  updateDashboard(user);
} catch (e) {
  console.error("Something failed:", e.message); // which line?
}

Use separate try/catch blocks for independent operations that can fail in different ways, or check each result before proceeding to narrow down where errors can come from.

Mistake 3: Throwing a non-Error value

Using throw "error string" instead of throw new Error("message").

throw "Invalid ID"; // not an Error object

Throwing a string is valid JavaScript, but the catch block receives a string, not an Error object. You lose access to error.message, error.name, and error.stack. Always throw new Error(...) or a class that extends Error.

Quick Recap

  • try/catch/finally wraps code that might fail — catch handles the error, finally always runs
  • The Error object has name (error type), message (description), and stack (trace) properties
  • Use throw new Error("message") to raise custom errors with descriptive messages
  • Never use empty catch blocks — always log or surface the error in some meaningful way

Ready to test your understanding? Take the quiz to reinforce what you learned.

© 2026 Ant Skillsv.26.07.07-23:01