Last 30 Days
No notifications
Bugs are inevitable — the goal is to FAIL LOUDLY at the right place and have the tools to find them fast. This page covers the built-in Error types, throwing and catching, custom error classes, async error handling, and a tour of Chrome DevTools (Sources panel, breakpoints, console, network).
# Errors & Debugging
Error
├── SyntaxError // bad syntax — usually caught at parse, can't catch with try/catch from same file
├── ReferenceError // using an undeclared variable
├── TypeError // wrong type — calling non-function, reading prop of null
├── RangeError // out of range — Array(-1), recursion too deep
├── URIError // bad URI in encodeURI / decodeURI
└── AggregateError // bundle of errors (Promise.any rejection)
Every Error has .name, .message, and a .stack (string trace).throw new Error("Something broke");
throw new TypeError("expected a number");
throw new RangeError("index out of bounds");// Don't throw raw strings/objects — you lose the stack
// throw "bad"; // ❌
try {
risky();
} catch (err) {
console.error(err.name, err.message);
if (err instanceof TypeError) { /* … */ }
} finally {
cleanup(); // always runs (success OR failure)
}
You can omit the binding (catch { … }) if you don't need the error.Error to add structured info:
class HttpError extends Error {
constructor(status, message) {
super(message);
this.name = "HttpError";
this.status = status;
}
}throw new HttpError(404, "User not found");
Catch with instanceof:
try { … }
catch (e) {
if (e instanceof HttpError && e.status === 404) showNotFound();
else throw e; // re-throw what you can't handle
}async functions, throws become rejected promises. Use try/catch around await.
async function load() {
try {
const res = await fetch("/api");
if (!res.ok) throw new HttpError(res.status, "API failed");
return res.json();
} catch (e) {
log(e);
throw e; // bubble up so callers can react
}
}For raw promises, attach .catch:
load().catch(showToast);window.addEventListener("unhandledrejection", e => {
console.error("Unhandled:", e.reason);
});
process.on("unhandledRejection", reason => {
console.error("Unhandled:", reason);
});.logconsole.log("plain");
console.warn("yellow");
console.error("red + stack");
console.info("blue");
console.debug("hidden by default");console.table([{a:1, b:2}, {a:3, b:4}]); // pretty rows
console.dir(obj, { depth: 4 }); // expandable tree
console.group("step 1"); … console.groupEnd();
console.time("t"); … console.timeEnd("t"); // duration
console.trace(); // stack at this point
console.assert(x > 0, "x must be positive");
$0 is the last selected DOM node; $_ is the last result.
2. Sources — set breakpoints by clicking the line number. Then *Step over* (F10) / *Step into* (F11) / *Resume* (F8).
3. Conditional / logpoint breakpoints — right-click line number → only break when x > 100, or log without pausing.
4. DOM breakpoints — break when an element is modified (subtree / attribute / removal).
5. Event listener breakpoints — break on every click, keyup, fetch, etc.
6. Network — see status, headers, payload, timing for every request. "Preserve log" survives reloads. "Slow 3G" simulates slow links.
7. Performance — record a profile, find what's slow.
8. Application — inspect localStorage, sessionStorage, cookies, IndexedDB, service workers.
9. debugger; statement — same as a breakpoint, but in your code..map file so DevTools shows the *original* source (your TS file) instead of the compiled output. Make sure they're enabled in dev.console.log vs *break*: a breakpoint lets you inspect the whole scope, not just one variable.
4. Bisect — comment out half the code, narrow it down.
5. git bisect if "it worked yesterday".