Error Handling

try/catch, custom errors, async error patterns, and building robust JavaScript applications

Last updated on

Errors happen — network failures, invalid input, unexpected data. Good error handling separates production-quality code from fragile scripts.

try / catch / finally

try {
  const data = JSON.parse(invalidJSON);
} catch (error) {
  console.error("Parse failed:", error.message);
} finally {
  console.log("Runs regardless of success or failure");
}

Error Types

TypeWhen
TypeErrorWrong type (null.foo, calling non-function)
ReferenceErrorAccessing undeclared variable
SyntaxErrorInvalid syntax (caught at parse time)
RangeErrorValue out of range (new Array(-1))
URIErrorInvalid URI encoding

Custom Error Classes

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

class NotFoundError extends Error {
  constructor(resource) {
    super(`${resource} not found`);
    this.name = "NotFoundError";
    this.statusCode = 404;
  }
}

// Usage
function validateEmail(email) {
  if (!email.includes("@")) {
    throw new ValidationError("email", "Invalid email format");
  }
}

try {
  validateEmail("invalid");
} catch (error) {
  if (error instanceof ValidationError) {
    console.error(`Field "${error.field}": ${error.message}`);
  }
}

Error Handling in Async Code

With async/await

async function fetchUser(id) {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.json();
  } catch (error) {
    console.error("Fetch failed:", error.message);
    return null; // fallback
  }
}

With Promises

fetch("/api/data")
  .then(res => {
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
  })
  .catch(err => console.error(err));

Global Error Handlers

// Browser — catch unhandled errors
window.addEventListener("error", (event) => {
  console.error("Uncaught:", event.message);
});

// Catch unhandled promise rejections
window.addEventListener("unhandledrejection", (event) => {
  console.error("Unhandled rejection:", event.reason);
  event.preventDefault(); // prevent default logging
});

Best Practices

  1. Only catch what you can handle — don't swallow errors silently
  2. Use specific error types — distinguish validation errors from network errors
  3. Always handle async errors — unhandled rejections crash Node.js
  4. Log errors with context — include what operation failed and with what data
  5. Use Error.cause (ES2022) — chain errors for better debugging:
try {
  await connectDatabase();
} catch (dbError) {
  throw new Error("App startup failed", { cause: dbError });
}

On this page