JS Sketchbooksee JavaScript think ✏️
← back to phase 5

5.8 — Error handling

Lesson 0.5 taught you to read errors — the machine's notes asking for help. Every error you've met since was thrown at you by the engine. Today the tables turn: you throw them — and you catch them.

Why would code deliberately crash? Because "limp onward with garbage" is worse: a withdraw(-500) that quietly proceeds corrupts an account; one that throws stops the wrongness at its source (5.2's loud-beats-silent philosophy, now as a tool you wield).

And here's the career secret hiding in this lesson: a failing test assertion is a throwexpect throws, the runner catches. You're about to learn the exact mechanism your future job runs on.

watch it happen
function withdraw(amount) {
  if (amount > 100) {
    throw new Error("limit is 100");
  }
  return "you got " + amount;
}

try {
  console.log(withdraw(50));
  console.log(withdraw(500));
  console.log("never reached");
} catch (err) {
  console.log("caught: " + err.message);
} finally {
  console.log("receipt printed");
}

The try block starts running its lines normally. withdraw(50) passes the guard, returns "you got 50", printed. A try block costs nothing when nothing goes wrong — it’s just a marked stretch of road with a safety net below.

the call stack (3.6’s tower) — errors travel DOWN itglobal — inside trywithdraw(50)the happy path: withdraw returns normally, the try block continuesyou got 50
under the hood

The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”

Error is a class with a family tree — 5.6's chains, live. TypeError, ReferenceError, SyntaxError all extends Error. That is why every error you've read since 0.5 had the same shape: .name, .message, .stack.

You can extend it too: class PaymentError extends Error lets a catch block tell YOUR errors apart with instanceof (5.6's chain question, at work).

Throw stops functions the way return does, but travels differently: return hands a value to the immediate caller; throw keeps falling through callers until a net appears.

Catch what you can handle, let the rest keep falling — catching everything and doing nothing ("swallowing") turns loud bugs back into silent ones, undoing the whole point.

One honest boundary: try/catch guards the synchronous stack. Errors born inside callbacks that run later (a setTimeout, a network reply) happen after your try has already finished. Catching those needs Phase 6's machinery — promises carry their errors, and await makes try/catch work on them again. Foreshadowed, on purpose.

your turn

⌨️ the division desk with a complaints window

Build a function that refuses impossible work by THROWING — and a caller that survives the refusal gracefully and always signs off.

requirements:

  • A function divide(a, b): if b is exactly 0, THROW a new Error with the message "cannot divide by zero". Otherwise return a / b.
  • In a try: print divide(10, 2), then print divide(5, 0).
  • In the catch: print the caught error's .message (just the message). In a finally: print "done".

when you press RUN, the console must show exactly:

5
cannot divide by zero
done

✏️ Quick check 1

Does "B" ever print? Type yes or no:

try {
  throw new Error("stop");
  console.log("B");
} catch (e) {
  console.log("C");
}

✏️ Quick check 2

Type exactly what this prints:

try {
  throw new Error("oven too hot");
} catch (e) {
  console.log(e.message);
}

✏️ Quick check 3

A try block succeeds with no error at all. Does its finally still run? Type yes or no:

teach it back

🗣️ Now teach it back

Explain to a friend what happens from the instant `throw` fires to the moment life is normal again — the stopping, the falling, the net, the finally — and why a failing test in a big suite doesn’t crash the other tests.

Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.

a few sentences, minimum — you’ve got this
to remember
throw = return’s emergency sibling: stop instantly, launch an Error object (class instance: .message, .name, .stack) DOWN the stack.
The first catch net in the fall path snags it — the rest of the try is skipped; caught = handled. Uncaught = the tower unwinds = crash.
finally runs on EVERY exit. And the job secret: expect() throws, test runners catch per test — red tests are this machinery.