Error Handling
Error Handling in JavaScript
Defensive programming with try/catch/finally, custom error hierarchies, and async failure handling.
The Acrobat and the Safety Net
Robust error handling prevents unexpected runtime exceptions from crashing your application, providing graceful degradation and informative debugging telemetry.
The Error Object Anatomy
Standard `Error` instances provide `name` (e.g., TypeError, SyntaxError), `message` (human-readable reason), and `stack` (the Call Stack trace).
try...catch...finally Mechanics
Code in `try` executes. If an error is thrown, control transfers immediately to `catch(err)`. The `finally` block executes unconditionally, even if `try` returns or `catch` throws.
Building Custom Error Classes
Extend `Error` (`class ValidationError extends Error`) to build typed errors with HTTP status codes and domain-specific metadata.
Asynchronous Error Trapping
`try...catch` only catches synchronous errors within its block. To catch asynchronous errors, wrap `await` calls inside `try...catch` or use `.catch()` on promise chains.
Creating custom domain errors and handling both synchronous and async exceptions:
// Custom Domain Error Class
class DatabaseError extends Error {
constructor(message, code) {
super(message);
this.name = "DatabaseError";
this.code = code;
}
}
// Handling async errors cleanly with try/catch/finally
async function loadUserProfile(userId) {
let connection = null;
try {
connection = { status: "connected" }; // Acquire resource
if (userId <= 0) {
throw new DatabaseError("Invalid User ID requested", "ERR_INVALID_ID");
}
return { id: userId, name: "Maria" };
} catch (err) {
if (err instanceof DatabaseError) {
console.error(`[${err.code}] Domain Error: ${err.message}`);
} else {
console.error("Unexpected failure:", err);
}
return null;
} finally {
// Guaranteed cleanup regardless of success or error
if (connection) {
connection.status = "closed";
console.log("Connection safely closed in finally block.");
}
}
}
loadUserProfile(-1);
[ERR_INVALID_ID] Domain Error: Invalid User ID requested
Connection safely closed in finally block.
Silent Catches (Swallowing Errors)
The Risk: Writing `catch (e) {}` with no logging or recovery hides critical bugs, making production issues impossible to debug.
The Fix: Always log caught errors or re-throw them after augmenting with context.
Catching Async Errors with Synchronous try/catch
The Risk: Wrapping a non-awaited async function or `setTimeout` inside `try...catch` fails to catch exceptions thrown after the current stack frame finishes.
The Fix: Always use `await` inside `try` blocks when calling async functions.
- ✓ `try` monitors code; `catch` handles failures; `finally` always runs.
- ✓ `finally` executes even if `try` contains a `return` statement.
- ✓ Use `instanceof` to catch specific error subclasses.
- ✓ Always `await` promises inside `try...catch` blocks.
- ✓ Global listeners: `window.onerror` in browsers, `process.on("unhandledRejection")` in Node.js.