A
AiTechWorlds
AiTechWorlds
Robust JavaScript handles errors gracefully it doesn't crash the entire application when something unexpected happens. This lesson covers synchronous and asynchronous error handling, custom error types, and defensive programming patterns.
try {
// Code that might throw
const data = JSON.parse(malformedJson);
processData(data);
} catch (error) {
// Runs when an error is thrown
console.error("Failed to parse:", error.message);
} finally {
// ALWAYS runs — error or not
closeConnection();
}try {
null.toString(); // TypeError
} catch (e) {
e.name; // "TypeError"
e.message; // "Cannot read properties of null"
e.stack; // full stack trace (string)
}
// Built-in error types
new Error("generic error");
new TypeError("wrong type");
new RangeError("value out of range");
new SyntaxError("bad syntax");
new ReferenceError("variable not defined");
new URIError("bad URL encoding");class AppError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.name = "AppError";
this.statusCode = statusCode;
}
}
class NotFoundError extends AppError {
constructor(resource) {
super(`${resource} not found`, 404);
this.name = "NotFoundError";
this.resource = resource;
}
}
class ValidationError extends AppError {
constructor(field, message) {
super(`Validation failed: ${message}`, 400);
this.name = "ValidationError";
this.field = field;
}
}
// Usage
function getUser(id) {
const user = users.find(u => u.id === id);
if (!user) throw new NotFoundError(`User #${id}`);
return user;
}
// Handle different error types
try {
const user = getUser(99);
} catch (err) {
if (err instanceof NotFoundError) {
showNotFound();
} else if (err instanceof ValidationError) {
showFieldError(err.field, err.message);
} else {
throw err; // re-throw unexpected errors
}
}// async/await with try/catch
async function loadDashboard(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(userId);
return { user, posts };
} catch (err) {
if (err.status === 401) redirectToLogin();
else if (err.status === 404) throw new NotFoundError(`User #${userId}`);
else throw err; // unexpected errors bubble up
}
}
// Promise chains with .catch
fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => render(posts))
.catch(err => handleError(err));
// Unhandled promise rejections — catch globally
window.addEventListener("unhandledrejection", (event) => {
console.error("Unhandled promise rejection:", event.reason);
event.preventDefault(); // prevent default browser behavior
logToErrorService(event.reason);
});// Promise.all — fails fast on any rejection
try {
const [user, posts, settings] = await Promise.all([
fetchUser(id),
fetchPosts(id),
fetchSettings(id)
]);
} catch (err) {
// Any one failure cancels all (others' results are discarded)
handleError(err);
}
// Promise.allSettled — handles mixed success/failure
const results = await Promise.allSettled([
fetchUser(id),
fetchPosts(id),
fetchSettings(id) // this one might fail
]);
results.forEach(result => {
if (result.status === "fulfilled") {
use(result.value);
} else {
console.error("Failed:", result.reason);
}
});// Result type pattern — avoid exceptions for expected failures
function parseJSON(str) {
try {
return { ok: true, value: JSON.parse(str) };
} catch {
return { ok: false, error: "Invalid JSON" };
}
}
const result = parseJSON(userInput);
if (result.ok) {
process(result.value);
} else {
showError(result.error);
}
// Retry with exponential backoff
async function fetchWithRetry(url, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (err) {
if (attempt === maxRetries - 1) throw err; // last attempt
const delay = 2 ** attempt * 1000; // 1s, 2s, 4s
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}// Catch synchronous errors that escape try/catch
window.onerror = (message, source, line, col, error) => {
reportError({ message, source, line, col, stack: error?.stack });
return true; // prevents default browser error display
};
// Catch unhandled promise rejections
window.addEventListener("unhandledrejection", (event) => {
reportError(event.reason);
});
// In Node.js
process.on("uncaughtException", (err) => {
console.error("Uncaught exception:", err);
process.exit(1); // crash with non-zero code
});
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled rejection:", reason);
});// Throw for programmer errors (should never happen in correct code)
function getUser(id) {
if (typeof id !== "number") {
throw new TypeError(`Expected number, got ${typeof id}`);
}
// ...
}
// Return null/undefined for expected "not found" situations
function findUser(id) {
return users.find(u => u.id === id) ?? null;
}
// Return a result object for user-facing validation
function validateEmail(email) {
if (!email) return { valid: false, error: "Email is required" };
if (!/@/.test(email)) return { valid: false, error: "Must contain @" };
return { valid: true };
}
// Throw for unrecoverable failures that need to bubble up
async function getAuthenticatedUser() {
const token = localStorage.getItem("token");
if (!token) throw new AuthError("Not authenticated"); // calling code must handle
const res = await fetch("/api/me", { headers: { Authorization: `Bearer ${token}` } });
if (!res.ok) throw new AuthError("Token invalid");
return res.json();
}Next lesson: JavaScript Design Patterns reusable solutions to common problems.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises