Skip to the notes
JSGroundwork
JSGroundwork handwritten · web dev
✎Playground→⌘Problems↻Review🔥Progress

Chapters

27 chapters
⌕
Beginner9›
B1Setup & mental modelB2Types & valuesB3Operators & flowB4Functions (first half)B5Objects & arrays (first half)B6DOM & eventsB7Basic asyncB8Errors & tools★Cheat page
Intermediate10›
I1Scope & functions, properlyI2Objects deeplyI3Prototypes & OOPI4Async, properlyI5Modules & toolingI6Regex, dates & APIsI7Error handlingI8Real-time connectionsI9Offline & storage★Cheat page
Advanced10›
A1Engine & memoryA2Advanced asyncA3MetaprogrammingA4Types & dataA5Patterns & architectureA6PerformanceA7SecurityA8EcosystemA9Testing★Cheat page
/ search[ ] chaptert top

JavaScript levels

1Beginner2Intermediate3Advanced

Ready to read

JSJavaScript⑂Git◎Interview prepΣDSA in JSSDSystem Design
More topics15›
</>HTML{ }CSS⚛ReactNNext.jsNeNest.jsTSTypeScriptNoNode.js🐳DockerDBSQL & Databases✓Testing🔒Web Security☁Cloud & DevOps◈GraphQL◆Redis☸Kubernetes
100%
B8

Errors & tools

The beginner track's last stop — reading what the engine is trying to tell you.

try / catch / finally

try {
  JSON.parse("this isn't JSON");     // throws a SyntaxError
} catch (error) {
  console.log("caught:", error.message);
} finally {
  console.log("finally always runs — success, failure, doesn't matter");
}
try {
  throw new Error("inner");
} finally {
  console.log("finally ran");
}

Click run — you'll see "finally ran", and then the error still shows up as uncaught below it. There's no catch here at all, and finally doesn't stop the error from propagating — it just guarantees that cleanup code (closing a connection, hiding a spinner) runs on the way out, whether the block succeeded or not.

The caught value doesn't have to be named if you don't need it — useful when you only care that something failed:

try {
  riskyThing();
} catch {                 // no (error) — the binding is optional since ES2019
  showFallbackUI();
}

Custom errors

Error is a class like any other — extend it to attach your own data, and instanceof still recognizes the whole chain.

class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

try {
  throw new ValidationError("age must be positive", "age");
} catch (e) {
  console.log(e.name, "-", e.message, "- field:", e.field);
  console.log("is an Error:", e instanceof Error);
  console.log("is a ValidationError:", e instanceof ValidationError);
}

Both instanceof checks come back true — super(message) wires up the normal Error machinery (.message, .stack), and the class ... extends Error keeps the prototype chain intact. That lets calling code catch broadly (instanceof Error) or specifically (instanceof ValidationError) depending on what it actually needs to handle differently.

Reading a stack trace

Every Error carries a .stack string — a snapshot of every function call that was still active the moment it was thrown, most-recent first:

Error: Cannot read properties of undefined (reading 'name')
    at getDisplayName (utils.js:12:18)
    at renderUser (UserCard.js:8:24)
    at renderApp (App.js:22:3)
    at main (index.js:5:1)

Read it top to bottom, most specific first: line 1 is where the error actually happened — inside getDisplayName, at utils.js line 12. Every line under it is a caller, in order, all the way out to where the whole chain started. The bug is almost always at or near the top; the rest of the trace is just "how did we get here."

⚠ The throw site isn't always the bug A TypeError reading a property of undefined tells you where it blew up, not where it went wrong. The real bug is usually a few frames up — whatever handed getDisplayName an object it shouldn't have. Read the whole trace before fixing the top line.

console — more than .log

Call For
console.log(...)general output
console.info(...), console.debug(...)same as log, different icon — some filters hide/show them separately
console.warn(...)yellow, doesn't stop anything — a heads-up
console.error(...)red, includes a stack trace automatically
console.table(data)an array of objects, rendered as an actual table
console.group(label) / .groupEnd()indents everything between them — collapsible in DevTools
console.time(label) / .timeEnd(label)how long the code between them took
console.table([
  { name: "Ana", age: 29, role: "admin" },
  { name: "Ravi", age: 34, role: "editor" },
]);

Open your own DevTools console and run that — every object becomes a row, every shared key becomes a column, automatically. It's the single fastest way to eyeball an array of records without writing a loop just to look at it.

DevTools, the short version

  • Elements panel — the live DOM tree, editable in place. Change a class or a style here to test an idea before touching the file.
  • Console panel — everything above, plus a REPL you can run arbitrary code in, against the actual page that's open.
  • Sources panel — set a real breakpoint by clicking a line number, or drop debugger; directly in your code. Either one pauses execution right there, with every variable in scope inspectable.
  • Network panel — every request the page made, its status, timing, and response — the first place to look when "the data never showed up."
Say it like this → "console.log tells you what you thought to ask for. A breakpoint lets you stop time and inspect everything — including the things you didn't think to log."
Practice this layer

Opens in the editor — write it, run it, and check it against real tests.

Parse JSON without crashing3 tests · beginnerThrow a real, typed error3 tests · beginner
←previousBasic async↑ CovernextScope & functions, properly→