JavaScript
2 min read
Updated 4 Aug 2026
Part 15 — Error Handling
Error types, Custom errors 🟢, Async errors ⚠️ 🎯
try {
risky();
} catch (err) { // ES2019+: catch binding is optional: `catch {`
console.error(err.message);
} finally {
cleanup(); // always runs (even on return/throw)
}
throw new Error("boom"); // throw anything, but prefer Error objects
Error types#
Error, TypeError, RangeError, ReferenceError, SyntaxError, URIError, EvalError, and AggregateError (from Promise.any). Each has .name, .message, .stack, and (modern) .cause.
Custom errors 🟢#
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
try {
throw new ValidationError("email required", "email");
} catch (e) {
if (e instanceof ValidationError) console.log(e.field);
}
// error chaining (ES2022)
throw new Error("failed to load config", { cause: originalError });
Async errors ⚠️ 🎯#
// try/catch does NOT catch errors from a rejected promise you didn't await
try { doAsync(); } catch {} // ❌ won't catch async rejection
try { await doAsync(); } catch {} // ✅ awaited → caught
// .catch for promise chains
fetch(url).then(r => r.json()).catch(err => /* handle */);
// finally on promises
p.finally(() => spinner.hide());
Unhandled rejections: browser fires window.onunhandledrejection; Node emits unhandledRejection (and by default crashes on it in recent versions).
Best practices: throw Error subclasses, never swallow errors silently, add context via cause, fail fast, handle at the boundary (route/UI layer), and don't use exceptions for normal control flow.
Interview questions (Part 15):
- Does
finallyrun iftryreturns? If it throws? - Why doesn't
try/catchcatch an un-awaited async error? - How do you create and detect a custom error type?