Throw Error objects (never strings), catch where you can actually do something about it, and give your own failures meaningful classes.
try / catch / finally
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));
}
}age 42
checked "42"
RangeError - invalid age: -3
checked "-3"
RangeError - invalid age: abc
checked "abc"Built-in error types
| Type | Thrown when |
|---|---|
SyntaxError | Code (or JSON.parse input) cannot be parsed |
ReferenceError | An undeclared variable, or a TDZ access |
TypeError | A value is the wrong type: calling a non-function, reading a property of undefined, writing a read-only property |
RangeError | A number out of range: new Array(-1), toFixed(200), stack overflow |
URIError | decodeURIComponent('%') |
AggregateError | Several errors at once (Promise.any) |
EvalError | Legacy, practically unused |
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}`); }
}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
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);DatabaseError | could not load user 7 | connection reset
true true true
NotFoundError 404 Order 42 not foundUncaught errors
An exception nobody catches ends the script (in Node the process exits with code 1). The build confirms this example really fails:
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");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.0Async errors
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));caught: async failure
caught via .catch: async failure| Safety net | Environment |
|---|---|
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?