JS

Error Handling

JavaScript · 8 entries

try / catch / finally

syntax
try { ... }
catch (error) { ... }
finally { ... }
example
function parseConfig(jsonString) {
  try {
    return JSON.parse(jsonString);
  } catch (error) {
    console.error("Invalid config JSON:", error.message);
    return {};
  } finally {
    console.log("Config parsing attempted.");
  }
}

const config = parseConfig('{"debug": true}');
console.log(config);
output
"Config parsing attempted."
{ debug: true }

Note finally always runs, whether the try succeeded or catch was triggered. It even runs if try or catch contains a return statement.

Throwing Errors

syntax
throw new Error(message);
throw new TypeError(message);
example
function withdraw(account, amount) {
  if (amount <= 0) {
    throw new RangeError("Withdrawal amount must be positive");
  }
  if (amount > account.balance) {
    throw new Error("Insufficient funds");
  }
  account.balance -= amount;
  return account.balance;
}

Note Always throw Error objects (not strings) so you get a stack trace. Use specific error types (TypeError, RangeError) when appropriate.

Custom Error Classes

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

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

try {
  throw new ValidationError("email", "Invalid email format");
} catch (err) {
  if (err instanceof ValidationError) {
    console.log(`Field: ${err.field}, Message: ${err.message}`);
  }
}
output
"Field: email, Message: Invalid email format"

Note Custom errors let you add context (which field, which resource) and enable precise catch handling with instanceof checks.

Error Cause (error.cause)

syntax
throw new Error(message, { cause: originalError });
example
async function loadUserData(userId) {
  try {
    const res = await fetch(`/api/users/${userId}`);
    return await res.json();
  } catch (err) {
    throw new Error("Failed to load user data", { cause: err });
  }
}

try {
  await loadUserData(42);
} catch (err) {
  console.error(err.message);        // "Failed to load user data"
  console.error(err.cause?.message);  // original error message
}

Note The cause option (ES2022) lets you wrap errors while preserving the original. Essential for debugging errors that propagate through layers.

Built-in Error Types

syntax
TypeError | RangeError | ReferenceError | SyntaxError | URIError | EvalError | AggregateError
example
// TypeError - wrong type for an operation
try { null.toString(); }
catch (e) { console.log(e.constructor.name); } // "TypeError"

// RangeError - value out of allowed range
try { new Array(-1); }
catch (e) { console.log(e.constructor.name); } // "RangeError"

// ReferenceError - undeclared variable
try { console.log(undeclaredVar); }
catch (e) { console.log(e.constructor.name); } // "ReferenceError"

Note Catching specific types helps you handle known errors differently from unexpected ones. AggregateError wraps multiple errors (used by Promise.any()).

Optional Catch Binding

syntax
try { ... } catch { ... }
example
function tryParseJSON(str) {
  try {
    return JSON.parse(str);
  } catch {
    return null;
  }
}

console.log(tryParseJSON('{"valid": true}'));  // { valid: true }
console.log(tryParseJSON("not json"));         // null

Note Since ES2019, you can omit the error parameter in catch if you do not need it. Keeps code cleaner for "swallow and fallback" patterns.

Centralized Async Error Handling

syntax
process.on('unhandledRejection', handler)
window.addEventListener('unhandledrejection', handler)
example
// Browser global handler
window.addEventListener("unhandledrejection", (event) => {
  console.error("Unhandled rejection:", event.reason);
  event.preventDefault(); // prevents console error
  reportToService(event.reason);
});

// Node.js global handler
process.on("unhandledRejection", (reason, promise) => {
  console.error("Unhandled rejection at:", promise, "reason:", reason);
});

Note Global handlers are your safety net -- they should log and report, not silently swallow errors. Always prefer local try/catch for known async operations.

Working with Stack Traces

syntax
error.stack
error.message
error.name
example
function deepFunction() {
  throw new Error("Something broke");
}

try {
  deepFunction();
} catch (err) {
  console.log(err.name);     // "Error"
  console.log(err.message);  // "Something broke"
  console.log(err.stack);    // Full stack trace string
}

Note err.stack is not standardized but is supported everywhere in practice. It includes the error message and the call chain leading to where the error was created.