9.1 try / catch / finally
Uncaught exceptions immediately terminate script execution, abort ongoing background tasks, and leave user interfaces in broken, unresponsive states. JavaScript's try...catch...finally construct allows applications to anticipate potential failure points, intercept runtime exceptions, and perform deterministic resource cleanup.
Figure 9.1: If an error is thrown in try, execution immediately redirects to catch. In all scenarios, finally executes unconditionally before exiting.
1. Mechanics of the Exception Lifecycle
try: Wraps statements that could potentially fail (parsing JSON, accessing network payloads, reading undefined properties).catch (error): Executes only if an exception is thrown inside thetryblock. Since ES2019, the error variable is optional (catch { ... }) if the error payload is unneeded.finally: Executes unconditionally. It runs whether an error occurred or not, and even ifreturnwas called insidetryorcatch!
2. The Critical Asynchronous Trap
A classic placement interview gotcha: try...catch is completely synchronous. It cannot catch errors thrown inside asynchronous callbacks like setTimeout or unchained Promises because the try block has already finished executing and exited the call stack!
3. The finally Return Override Trap
If a finally block explicitly executes a return statement, that return value silently swallows and overrides any return value or pending uncaught exception from the try or catch block.
// 1. Synchronous Exception Interception
function parseUserData(rawJson) {
try {
const data = JSON.parse(rawJson);
return data.user;
} catch (err) {
console.warn("JSON Parse Error:", err.message);
return null; // Safe fallback value
} finally {
console.log("Cleanup: Parsing operation completed.");
}
}
// 2. The Async Gotcha: WHY THIS FAILS:
try {
setTimeout(() => {
// throw new Error("Boom!"); // UNCAUGHT! try block already exited!
}, 100);
} catch (err) {
console.log("Never reaches here!");
}
// 3. The Correct Async Solution: async / await
async function fetchSecureData() {
try {
const res = await fetch("https://api.readytogo.dev/data");
const json = await res.json();
return json;
} catch (err) {
console.error("Caught async network error:", err.message);
} finally {
console.log("Hide loading spinner");
}
}
9.2 Custom Errors
While JavaScript provides built-in error types (TypeError, ReferenceError, SyntaxError), production enterprise applications require domain-specific error classifications (e.g. ValidationError, AuthenticationError, DatabaseError) to differentiate operational failures from programming bugs and respond with precise HTTP status codes.
Figure 9.2: Subclassing the native Error class allows custom categories with metadata, clean stack traces, and cause chaining.
1. Subclassing the Built-in Error Class
A production custom error must satisfy three requirements:
- Call
super(message)to properly initialize the baseErrorinstance and stack trace. - Explicitly assign
this.name = this.constructor.name(by default,Errorsetsname = "Error"). - In V8 runtimes (Node.js/Chrome), invoke
Error.captureStackTrace(this, this.constructor)to keep the custom constructor out of the call stack trace.
2. Error Cause Chaining (ES2022)
When catching a low-level error (e.g., a raw database connection timeout), wrapping it in a domain error without losing the original stack trace used to be tedious. In ES2022, passing { cause: originalError } links the root failure directly to error.cause.
// 1. Defining a Domain-Specific HTTP Error Class
class HttpError extends Error {
constructor(statusCode, message, options) {
super(message, options); // Supports options.cause in ES2022!
this.name = this.constructor.name;
this.statusCode = statusCode;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
class NotFoundError extends HttpError {
constructor(resource = "Resource", options) {
super(404, resource + " was not found on this server.", options);
}
}
// 2. Handling with instanceof Type Checking
function findUserById(id) {
if (id !== 42) {
throw new NotFoundError("User with ID " + id);
}
return { id: 42, username: "admin" };
}
try {
findUserById(99);
} catch (err) {
if (err instanceof NotFoundError) {
console.warn("HTTP " + err.statusCode + ": " + err.message);
} else {
console.error("Unexpected runtime panic:", err);
}
}