Skip to content
  • Error Handling & Custom Errors · JavaScript

Error Handling & Custom Errors

LearnViaNotes @learnvianotes· 3 min read

Throw Error objects (never strings), catch where you can actually do something about it, and give your own failures meaningful classes.

try / catch / finally

JavaScript
function parseAge(input) {
  const n = Number(input);
  if (!Number.isInteger(n) || n < 0) throw new RangeError(`invalid age: ${input}`);
  return n;
}
for (const input of ["42", "-3", "abc"]) {
  try {
    console.log("age", parseAge(input));
  } catch (err) {
    console.log(err.name, "-", err.message);
  } finally {
    console.log("  checked", JSON.stringify(input));
  }
}
Output
age 42
  checked "42"
RangeError - invalid age: -3
  checked "-3"
RangeError - invalid age: abc
  checked "abc"

Built-in error types

TypeThrown when
SyntaxErrorCode (or JSON.parse input) cannot be parsed
ReferenceErrorAn undeclared variable, or a TDZ access
TypeErrorA value is the wrong type: calling a non-function, reading a property of undefined, writing a read-only property
RangeErrorA number out of range: new Array(-1), toFixed(200), stack overflow
URIErrordecodeURIComponent('%')
AggregateErrorSeveral errors at once (Promise.any)
EvalErrorLegacy, practically unused
JavaScript
const attempts = [
  () => null.name,
  () => undefinedVariable,
  () => (1).toFixed(500),
  () => decodeURIComponent("%"),
  () => JSON.parse("{bad json}"),
];
for (const fn of attempts) {
  try { fn(); } catch (e) { console.log(`${e.name}: ${e.message}`); }
}
Output
TypeError: Cannot read properties of null (reading 'name')
ReferenceError: undefinedVariable is not defined
RangeError: toFixed() digits argument must be between 0 and 100
URIError: URI malformed
SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)

Custom error classes

JavaScript
class AppError extends Error {
  constructor(message, options) {
    super(message, options);            // options.cause (ES2022)
    this.name = this.constructor.name;
  }
}
class NotFoundError extends AppError {
  constructor(resource, id) {
    super(`${resource} ${id} not found`);
    this.status = 404;
  }
}
class DatabaseError extends AppError {}

function findUser(id) {
  try {
    throw new Error("connection reset");        // low-level failure
  } catch (cause) {
    throw new DatabaseError(`could not load user ${id}`, { cause });
  }
}
try {
  findUser(7);
} catch (e) {
  console.log(e.name, "|", e.message, "|", e.cause.message);
  console.log(e instanceof DatabaseError, e instanceof AppError, e instanceof Error);
}
const nf = new NotFoundError("Order", 42);
console.log(nf.name, nf.status, nf.message);
Output
DatabaseError | could not load user 7 | connection reset
true true true
NotFoundError 404 Order 42 not found

Uncaught errors

An exception nobody catches ends the script (in Node the process exits with code 1). The build confirms this example really fails:

JavaScript
function divide(a, b) {
  if (b === 0) throw new Error("Division by zero");
  return a / b;
}
console.log(divide(10, 2));
console.log(divide(1, 0));   // uncaught → crash
console.log("never reached");
Terminal (stack trimmed) — exit code 1
5
file:///…/main.mjs:2
  if (b === 0) throw new Error("Division by zero");
                     ^

Error: Division by zero
    at divide (file:///…/main.mjs:2:22)
    at file:///…/main.mjs:6:13

Node.js v24.12.0

Async errors

JavaScript
try {
  setTimeout(() => { /* throw here would NOT be caught below */ }, 0);
} catch {
  console.log("never");
}
async function load() { throw new Error("async failure"); }
try {
  await load();                              // await makes try/catch work
} catch (e) {
  console.log("caught:", e.message);
}
load().catch((e) => console.log("caught via .catch:", e.message));
Output
caught: async failure
caught via .catch: async failure
Safety netEnvironment
window.addEventListener('error', …)Browser: uncaught sync errors
window.addEventListener('unhandledrejection', …)Browser: rejected promises without a handler
process.on('uncaughtException') / ('unhandledRejection')Node: log and exit — don't keep running in an unknown state

Can try/catch catch errors thrown inside a setTimeout callback?

What does finally do if both try and finally return?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report