Robust programs fail loudly and recover deliberately instead of crashing or hiding problems.
When something goes wrong, code throws an error, which unwinds the call stack until a try...catch handles it. If nothing does, the error is reported as uncaught (and a Node.js process exits).
try {
riskyThing();
} catch (err) {
console.error(err.message);
} finally {
cleanUp(); // always runs
}You can throw any value, but always throw Error objects (or subclasses): they carry message, name and a stack trace. Built-in types include TypeError, RangeError, SyntaxError and ReferenceError.
finally runs whether the try block finishes normally, returns or throws — use it to release resources. If you don't need the error, the binding is optional: catch { ... }.
For your own failure cases, subclass Error so callers can tell them apart with instanceof:
class NotFoundError extends Error {
name = "NotFoundError";
}When you catch a low-level error and throw a more meaningful one, keep the original with the `cause` option: throw new Error("Could not load config", { cause: err }). Debuggers and Node print the whole chain.
Guidelines that save real pain:
- Catch only what you can handle; let the rest propagate.
- Never swallow errors silently with an empty
catch. - Validate input at boundaries and throw early with a clear message.
- A
tryaround code that schedules a callback does not catch errors thrown later inside it — asynchronous errors are handled with promises andawait, coming up next.
Example
class ValidationError extends Error {
name = "ValidationError";
constructor(field, message) {
super(message);
this.field = field;
}
}
function parseAge(input) {
const age = Number(input);
if (!Number.isInteger(age) || age < 0) {
throw new ValidationError("age", `Invalid age: ${input}`);
}
return age;
}
function loadProfile(raw) {
try {
return { age: parseAge(raw.age) };
} catch (err) {
if (err instanceof ValidationError) return { age: null, error: err.message };
throw new Error("Unexpected profile failure", { cause: err });
} finally {
console.log("validated", raw);
}
}
console.log(loadProfile({ age: "42" })); // { age: 42 }
console.log(loadProfile({ age: "-1" })); // { age: null, error: 'Invalid age: -1' }Remember
- Throw
Errorobjects (or subclasses), never strings finallyalways runs — use it for cleanup- Wrap low-level errors with
{ cause }instead of discarding them
Watch out for
- An empty
catch {}that hides bugs. - Expecting a synchronous try/catch to catch errors thrown later in a callback.