Notifications

No notifications

/Phase 4

Errors & Debugging

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).

On this page

Detailed Theory

# Errors & Debugging

The Error hierarchy

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).

Throwing

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 / catch / finally

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.

Custom errors

Subclass 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
}

Errors in async code

Inside 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);

Unhandled rejections

Always catch — unhandled rejections crash Node and log warnings in browsers.
window.addEventListener("unhandledrejection", e => {
    console.error("Unhandled:", e.reason);
});
process.on("unhandledRejection", reason => {
    console.error("Unhandled:", reason);
});

Console — more than just .log

console.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");

Chrome DevTools — the tools you'll actually use

1. Console$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.

Source maps

Bundlers ship a .map file so DevTools shows the *original* source (your TS file) instead of the compiled output. Make sure they're enabled in dev.

A small debugging checklist

1. Read the error message and the stack — don't guess. 2. Reproduce in the smallest test (or a REPL). 3. 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".