9.3 Error Handling and Debugging

Aligned outcomes:

SLO 3

Describe, design, implement, and test structured programs using currently accepted methodology.

SLO 4

Explain what an algorithm is and its importance in computer programming.

Learning Objectives

By the end of this section, you will be able to:

In this section, you will learn to:
  • explain why an error inside scheduled code escapes an enclosing try...catch, and where to put the try instead;
  • write a catch block with no binding, for a handler that never looks at the error;
  • apply the rethrowing pattern so a catch handles only the errors it recognizes and passes the rest along;
  • describe what window.onerror is for and what it receives;
  • read the message a model editor reports and narrow a broken model down to the line that produced it.

9.3.1 What You Already Know

Section 2.5 built the try...catch construct from the ground up, and none of it has changed. This is the whole of it, in one program:

Editor
runs in your browser
▶ Press Run to see the output…

What you should see:

Attempting the risky work.
Caught a ReferenceError
It said: missingValue is not defined
finally runs either way.
And the program carries on.

Four pieces, and you have met all four:

Piece What it does Taught in
try runs the risky code; abandons the rest of its block at the first error 2.5.2
catch (err) runs instead, with an error object describing what went wrong 2.5.2, 2.5.3
throw raises an error from your own code 2.5.4
finally runs afterwards no matter which of the two happened 2.5.5
This section is the second half

Chapter 2 taught you to catch an error. This one is about the cases that construct does not cover — the error that escapes it entirely, and the error nobody was there to catch.

So the question now is not how do I catch an error. It is what happens to the errors try...catch misses, which is four more things:

  1. an error in code that runs later is not inside your try when it happens (9.3.2);
  2. a catch you never read from does not need a binding at all (9.3.3);
  3. a catch that handles everything hides the bugs it did not expect (9.3.4);
  4. and something should still be watching when an error escapes every handler you wrote (9.3.5).

9.3.2 Errors That Escape

Section 2.5.6 named two limits of try...catch. The first was the syntax error: the program never starts, so there is no running program in which a catch could fire. The second was left for this section, because it needs functions — which you now have.

A try...catch guards the lines inside it, at the moment those lines run.

Some code does not run at the moment you write it. setTimeout(fn, ms) is the plainest example: it hands JavaScript a function and a delay, and JavaScript runs that function later, on its own. setTimeout itself returns immediately.

try {
  setTimeout(function() {
    console.log(missingValue);          // the error happens in here
  }, 1000);
} catch (err) {
  console.log("This never prints.");
}

What you should see: the catch does not run, and an uncaught ReferenceError reaches the browser one second later.

Follow the timing, because the timing is the whole explanation:

  1. setTimeout is called. It schedules the function and returns straight away — it does not run it.
  2. The try block is now finished. Execution leaves the whole try...catch behind.
  3. One second later, JavaScript runs the scheduled function. There is no try around it any more, because that block ended in step 2.
The try has to be there when the error is

A try...catch is not a fence around a variable or a function; it is a fence around a moment. When the scheduled code finally runs, that moment is long gone.

The fix follows directly from the diagnosis. Put the try...catch where the error will be — inside the scheduled function:

setTimeout(function() {
  try {
    console.log(missingValue);
  } catch (err) {
    console.log("Caught it: " + err.name);
  }
}, 1000);

What you should see: one second later, Caught it: ReferenceError.

Try It Now 9.3.1

A program schedules a job to run after half a second, and wraps the scheduling in a try...catch:

try {
  setTimeout(startJob, 500);
} catch (err) {
  console.log("Job failed to start.");
}

If startJob throws an error when it runs, does the catch block print anything? Where would the try...catch have to go to catch it?

Solution

Step 1 — find when the error happens: setTimeout(startJob, 500) only schedules startJob. It returns immediately, so the try block finishes right away, with no error in it.

Step 2 — find where execution is at that moment: Half a second later startJob runs and throws. By then the try...catch has been left behind, so nothing is guarding that code.

Answer: No, the catch prints nothing — the error is uncaught. The try...catch has to be inside startJob (or inside a wrapper function passed to setTimeout), so that it exists at the moment the error occurs.

9.3.3 A catch Without Its Binding

Every catch you have written so far named the error: catch (err). That name is a binding — it binds the error object to a variable so the block can read err.name and err.message.

Sometimes the handler does not care. It is enough to know that something failed:

Editor
runs in your browser
▶ Press Run to see the output…

What you should see:

That was not valid JSON. Using an empty object instead.
Carrying on.

Note the catch { — no parentheses, no name. This is the optional catch binding, and it is allowed exactly when the block never mentions the error object.

Leave out what you do not use

catch (err) { ... } with an unused err is not wrong, just noisy. Dropping the binding says plainly: I know this can fail, and the details do not change what I do about it.

Leave the binding in the moment you need any detail from it — the name, the message, or the type. The next subsection needs all three.

Try It Now 9.3.2

Which of these two catch blocks can drop its binding, and why?

// A
try { risky(); } catch (err) { console.log("Failed: " + err.message); }

// B
try { risky(); } catch (err) { console.log("Failed."); }
Solution

Step 1 — check what each block uses: Block A reads err.message, so it uses the error object. Block B prints a fixed string and never mentions err.

Step 2 — apply the rule: The binding may be omitted only when the block does not use the error object.

Answer: B can be written as catch { console.log("Failed."); }. A must keep catch (err), because it reads err.message.

9.3.4 Rethrowing What You Do Not Recognize

Here is a catch block that looks careful and is dangerous:

Editor
runs in your browser
▶ Press Run to see the output…

What you should see:

Bad JSON in the part file.

That message is a lie. The JSON is perfect. The real error is the typo part.nmae — a TypeError, because part.nmae is undefined and undefined has no .toUpperCase(). The catch caught it anyway, dressed it in the wrong explanation, and hid a typo that will now be very hard to find.

**A catch block catches everything thrown inside its try, not just the failure you had in mind.**

The fix is the rethrowing pattern, in three steps:

  1. catch every error, as always;
  2. check whether it is one you actually recognize;
  3. if it is not, throw it again so it travels on to someone who does.

instanceof is how step 2 asks what kind of error is this?

Editor
runs in your browser
▶ Press Run to see the output…

What you should see: a real, uncaught TypeError naming the actual problem, instead of a comforting sentence about JSON.

An honest crash beats a misleading message

A rethrown error is not a failure of your error handling. It is your error handling refusing to take credit for a problem it does not understand.

JSON.parse throws a SyntaxError when its text is malformed, so err instanceof SyntaxError is exactly the test for the failure this try was written for. Anything else is a bug somewhere in the code, and a bug wants to be seen.

A rethrown error is not necessarily fatal. If an outer try...catch is waiting, it gets its turn:

Editor
runs in your browser
▶ Press Run to see the output…

What you should see:

Axle
loadPart: the text was not JSON.
null
Outer handler caught a TypeError

The inner handler dealt with what it knew — malformed text — and let the TypeError from null.length travel out to the outer one.

Try It Now 9.3.3

A catch block handles a RangeError and rethrows everything else. Write its if statement.

Editor
runs in your browser
▶ Press Run to see the output…
Solution

Step 1 — test the type you recognize: Use instanceof against the error type the block is written for — RangeError.

Step 2 — rethrow the rest: Everything that fails that test goes back out with throw err.

Editor
runs in your browser
▶ Press Run to see the output…

What you should see:

A measurement was out of range.

Answer: if (err instanceof RangeError) { ...handle it... } else { throw err; }

9.3.5 The Last Line of Defense

Rethrowing raises a fair question: if every handler passes an error along, where does the last one go?

It reaches the browser, which stops the script. That is the right outcome — but it happens on your machine, where you can see the console. On a visitor's machine, nobody is watching.

Browsers offer one final hook for exactly that case:

window.onerror = function(message, url, line, col, error) {
  console.log("Unhandled: " + message + " at line " + line);
};

Assign a function to window.onerror and the browser calls it for errors that escaped every try...catch. It receives five arguments:

Argument What it holds
message the error message
url the address of the script the error came from
line the line number
col the column number
error the error object itself
A safety net, not a trampoline

window.onerror does not resume the broken program. Its job is to report — usually by logging the error to a server so a developer finds out that real users are hitting it.

That is the honest limit of it: by the time window.onerror runs, the work that failed has already failed. A try...catch in the right place can recover; a global handler can only tell you.

Try It Now 9.3.4

Why would you install window.onerror on a page that already uses try...catch carefully?

Solution

Step 1 — recall what a try...catch covers: Only the code inside it, at the moment that code runs. Anything you did not anticipate is not wrapped.

Step 2 — say what the global handler adds: It catches what escaped — including errors from code you never expected to fail — and reports them, so failures on someone else's machine do not go unnoticed.

Answer: Because careful handling only covers the failures you predicted. window.onerror is what tells you about the ones you did not.

9.3.6 Debugging a Model

Everything so far has been about errors your program handles. This last part is about errors you have to handle, at the keyboard, when a model does not appear.

A model editor on these pages runs your code and paints the result. When the code throws, the canvas stays blank and the message appears in the strip underneath it, in the same two-part shape you have been reading since Chapter 2:

ReferenceError: path2 is not defined

That is a name and a message, exactly like err.name and err.message. Read it the same way: the name says what kind of mistake, the message says which value was involved.

Three symptoms cover almost every broken model.

A message naming something "is not defined". A misspelled function, or one you have not imported. cuboid exists; cubiod does not.

"Stopped: this code ran too long — check that your loop can finish." The editor gives your code five seconds and then stops it. This is nearly always a loop whose counter never reaches its limit:

// i is never increased, so i < 8 is true forever
for (let i = 0; i < 8; ) {
  parts.push(box({ size: 2 }));
}

A blank canvas with no message at all. The code ran to the end without an error and produced no geometry — usually a main that computed a shape and forgot to return it.

The canvas is your output

In a model editor, console.log does not print into the page — its output goes to the browser's own developer console. To see what a model is doing, return the part you want to look at.

That last note is the technique worth keeping. When a complicated model comes out wrong, cut it down instead of staring at it: return an early piece, confirm it looks right, and add the next step back one at a time.

function main() {
  const base = box({ size: [40, 20, 5] });
  const post = tube({ radius: 3, height: 30 });

  return base;                   // check the base alone first,
  // return union(base, post);    then put this line back
}

The bug is in the first step whose result stops looking right. This is the same discipline as the spec-first flow in §9.4 — take small steps and check after each one — applied by hand instead of by a test.

Try It Now 9.3.5

A model editor shows a blank canvas and prints no message. The code has no misspelled names. What is the most likely cause, and how would you confirm it?

Solution

Step 1 — read the absence of a message: No message means no error was thrown. The code ran all the way through.

Step 2 — ask what was produced: A blank canvas after a clean run means no geometry reached the renderer — most often a main that builds a shape and never returns it.

Answer: The most likely cause is a missing return. Confirm it by returning a simple shape early in main: if that shape appears, the code runs fine and the original problem was the missing return.

Summary

Problem Set 9.3

Problem 1. A try block calls setTimeout with a function that throws. Explain why the enclosing catch block never runs.

Solution

Step 1 — Follow the timing of the scheduling call: setTimeout only schedules the function and returns immediately, so the try block finishes with no error inside it. A catch can only fire for an error thrown while its try block is executing — and at this moment none is.

Step 2 — Follow the timing of the throw: The scheduled function runs later, after execution has already left the whole try...catch behind. When it throws, there is no try around that code any more — a try...catch guards the moment its block runs, not the functions it mentioned.

Answer: The catch never runs because the error is thrown after the try...catch has already finished: setTimeout returns at once, and the throwing function runs later, when no try is active. The try would have to be inside the scheduled function to catch it.

Problem 2. Where must a try...catch be placed to catch an error thrown inside a scheduled function?

Solution

Step 1 — Identify where the error will be: The error is thrown at the moment the scheduled function runs, so the only try that can catch it is one that exists at that same moment.

Step 2 — Put the guard where the error will be: Place the try...catch inside the scheduled function itself:

setTimeout(function() {
  try {
    // the risky work happens in here
  } catch (err) {
    console.log("Caught it: " + err.name);
  }
}, 1000);

Answer: The try...catch must go inside the scheduled function — the code that runs later must itself be wrapped, so the guard exists when the error does.

Problem 3. State the rule for when a catch block may omit its binding.

Solution

Step 1 — State the rule: The catch binding may be omitted exactly when the handler never uses the error object — it does not read err.name, does not read err.message, and does not test the error's type in any way.

Step 2 — Note the flip side: The moment the handler needs any detail from the error, the binding must return as catch (err), because reading those details is precisely what the binding is for.

Answer: A catch may omit its binding — written as catch { ... }, with no parentheses and no name — when the block never mentions the error object.

Problem 4. Rewrite try { save(); } catch (err) { console.log("Could not save."); } using the optional catch binding.

Solution

Step 1 — Check that the rule applies: The handler prints a fixed string and never reads err, so the binding is unused and may legally be dropped.

Step 2 — Rewrite without the binding:

try {
  save();
} catch {
  console.log("Could not save.");
}

Answer: try { save(); } catch { console.log("Could not save."); } — same behaviour, but the bare catch { says plainly that the details of the error do not matter here.

Problem 5. Explain why a catch block that reports the same message for every error can hide a bug.

Solution

Step 1 — Recall what a catch actually catches: It catches everything thrown inside its try, not only the failure the author had in mind.

Step 2 — Apply it to the uniform message: Suppose the try was written for bad JSON, but a typo such as part.nmae throws a TypeError there instead. The one-size-fits-all handler reports the same "Bad JSON" explanation for the typo, so the printed cause is false and the real bug — the misspelled property — is buried behind a misleading message.

Answer: A catch that reports the same message for every error can hide a bug because it cannot tell which failure actually occurred: an unexpected error (like a typo's TypeError) is dressed in the explanation meant for the expected one, so the report lies and the true problem becomes very hard to find.

Problem 6. List the three steps of the rethrowing pattern in order.

Solution

Step 1 — List the steps in order:

  1. Catch every error, as always — the catch block gets the error whatever it is.
  2. Check whether it is one you actually recognize — typically with instanceof against the error type this try was written for.
  3. Rethrow what you do not recognizethrow err; in an else branch, so it travels on to someone who does understand it.

Answer: In order: catch everything → check with instanceof whether you recognize the error → throw err to pass on the rest.

Problem 7. Which operator does a catch block use to ask what kind of error it received, and what does it compare against?

Solution

Step 1 — Name the operator: instanceof is how a catch block asks what kind of error is this?

Step 2 — Say what it compares against: The caught error object on the left, an error type (a constructor) on the right — for example, err instanceof SyntaxError asks whether the caught error is a SyntaxError. Built-in types like TypeError and RangeError work the same way.

Answer: The catch uses instanceof, comparing the error against an error type — err instanceof SyntaxError is exactly the test for the failure this try was written for.

Problem 8. What happens to a rethrown error when an outer try...catch is waiting? What happens when none is?

Solution

Step 1 — The case with an outer handler: If an outer try...catch is waiting, the rethrown error travels to it and the outer handler gets its turn — that is how, in §9.3.4, the inner handler dealt with malformed text while the TypeError moved on to the outer catch.

Step 2 — The case with no handler: If no outer try...catch is waiting, the error reaches the browser, which stops the script. That is the honest outcome — and the situation window.onerror exists to report on.

Answer: With an outer try...catch waiting, the outer catch receives the rethrown error and can handle it. With none, the error reaches the browser, which stops the script.

Problem 9. What is window.onerror for, and what five arguments does it receive?

Solution

Step 1 — Say what it is for: window.onerror is the browser's last-resort handler, called for errors that escaped every try...catch on the page. Its job is to report — usually by logging the error to a server so a developer finds out — never to resume the broken program.

Step 2 — List the five arguments it receives, in order:

  • message — the error message
  • url — the address of the script the error came from
  • line — the line number
  • col — the column number
  • error — the error object itself

Answer: window.onerror reports errors that escaped every handler — it does not recover from them. It receives message, url, line, col and error: the error message, the script's address, the line number, the column number, and the error object.

Problem 10. A model editor shows a blank canvas with no error message and no misspelled names. Name the most likely cause and one way to confirm it.

Solution

Step 1 — Read the absence of a message: No error message means no error was thrown — the code ran all the way to the end. So the fault is not a misspelled name or an unhandled exception.

Step 2 — Ask what the run produced: A blank canvas after a clean run means no geometry reached the renderer — most often a main that built a shape and forgot to return it.

Step 3 — Confirm it: Return a simple shape early in main. If that simple shape appears on the canvas, the code itself runs fine, and the original problem was the missing return.

Answer: The most likely cause is a missing returnmain computed a shape but never handed it to the renderer. Confirm it by returning a simple shape early in main: if it appears, the code runs correctly and the missing return was the fault.

Key Terms

scheduled code — a function handed to something like setTimeout to be run later, after the code that scheduled it has finished.

optional catch binding — writing catch { ... } with no parentheses, allowed when the block never uses the error object.

rethrowing — passing an unrecognized error out of a catch block with throw err, so a handler that understands it can respond.

instanceof — the operator that tests what kind of value something is; in a catch block it tests the error against a type such as SyntaxError.

window.onerror — a browser handler called for errors that escaped every try...catch; it reports them and does not resume the program.

loop guard — the editor's five-second limit on a running fence, reported as "Stopped: this code ran too long".