Different errors and finally
Problem
The bill for zero people
The program splits the restaurant bill evenly. You already know about wrong input, and you catch it:
try { const bill = toInt(await ask("")); const people = toInt(await ask("")); console.log(`Per person: €${Math.trunc(bill / people)}`);} catch (e) { console.log("Enter whole numbers.");}The bill is €90, and someone enters 0 for the number of people:
Per person: €InfinityThe program does not stop, but it prints nonsense. In JavaScript division by zero does not throw. It gives Infinity, so catch has nothing to catch. And when the errors really are of different kinds, there is still only one catch. So how do you tell the customer exactly what is wrong?
The idea
One catch, several kinds
Zero is not an exception, so it is an if:
try { const bill = toInt(await ask("")); const people = toInt(await ask("")); if (people === 0) { console.log("People cannot be 0."); } else { console.log(`Per person: €${Math.trunc(bill / people)}`); }} catch (e) { console.log("Enter whole numbers.");}| Input | Output |
|---|---|
| 90 and 3 | Per person: €30 |
| 90 and 0 | People cannot be 0. |
| 90 and three | Enter whole numbers. |
But there are errors that JavaScript throws by itself. The receipt has a line of dashes, and its length is typed in. "-".repeat(-1) throws a RangeError, and toInt throws an Error. Two kinds, but there is only one catch after try. You check the kind inside it with instanceof ("is of the kind"):
const text = await ask("");try { const width = toInt(text); console.log("-".repeat(width));} catch (e) { if (e instanceof RangeError) { console.log("The length cannot be negative."); } else { console.log("Enter a whole number."); }}| Input | Output |
|---|---|
| 5 | ----- |
| -1 | The length cannot be negative. |
| five | Enter a whole number. |
The checks go from top to bottom, like any if / else if. The first branch whose kind matches runs, and only that one.
const text = await ask("");try { const width = toInt(text); console.log("-".repeat(width));} catch (e) { if (e instanceof RangeError) { console.log("The length cannot be negative."); } else { console.log("Enter a whole number."); }}Step by step, in words
- First run: "five" is typed in for the length of the line.
- toInt cannot make a number out of "five": an Error is thrown.
- The first check is for RangeError. It does not match, so it is skipped.
- The else matches. It runs, and only it.
- Second run: "-1" is typed in.
- toInt gives -1, and "-".repeat(-1) throws a RangeError.
- The very first check matches. The else is never reached.
- The checks go from top to bottom. The first one that matches runs.
More precisely, if you are curious
- Something you do not want to catch here, you throw again in
else:throw e;. Howthrowworks is in the next lesson.
The idea
The family of exceptions
Errors are classes, related by inheritance. Here are the most common ones:
Error├── TypeError wrong kind of value: undefined.seats├── RangeError out of range: "-".repeat(-1)├── SyntaxError bad syntax: JSON.parse("{")├── ReferenceError a name that does not exist├── URIError├── EvalError└── AggregateErrorinstanceof checks the kind you name and all of its subclasses. This is the polymorphism from the module on inheritance: an object of a subclass is also of the parent's kind.
const error = new RangeError("Invalid count value: -1");console.log(error instanceof RangeError);console.log(error instanceof Error);console.log(error instanceof TypeError);truetruefalsecatch catches everything, and e.name tells you the kind:
function printLine(widthText) { try { console.log("-".repeat(toInt(widthText))); } catch (e) { console.log(`${e.name}: ${e.message}`); }}
printLine("5");printLine("-1");printLine("five");-----RangeError: Invalid count value: -1Error: "five" is not a whole number.e.name is the name of the kind of error: RangeError from repeat, Error from toInt.
More precisely, if you are curious
- In JavaScript any value can be thrown, even a string, so in
catchtheeis not always anError. You will see this in the next lesson.
- Catch as specifically as you can. A catch that does not check the kind also hides errors you did not expect, such as a bug in your own code.
Try it
Which catch?
What will the program print?
const tables = [{ seats: 4 }, { seats: 2 }, { seats: 6 }];try { console.log(`Table 4: ${tables[3].seats} seats`);} catch (e) { if (e instanceof RangeError) { console.log("Out of range"); } else if (e instanceof TypeError) { console.log("Wrong value"); } else { console.log("General error"); }}tables[3] does not throw. It is undefined. The error comes at .seats: you cannot read from undefined, and that is a TypeError. It is not a RangeError, so the first check does not pass. The second one passes and runs. The else is never reached, even though it would catch it too.Try it
Find the bug
text = "-1" the program prints "Something went wrong." and not "The length cannot be negative.". Which line is the bug on?Pick the line with the bug.
RangeError is a subclass of Error, so e instanceof Error is true for it too. The first check catches everything, and the second can never be reached. JavaScript says nothing: the program runs and just prints the general message. The rule is from the specific to the general: swap the two checks.The idea
Nobody makes you
Java has checked exceptions: the compiler does not accept the program until you catch them or declare them with throws. JavaScript has none of those. No error has to be caught:
function printReceipt() { console.log("Printing the receipt..."); console.log("-".repeat(20)); console.log("Done.");}
printReceipt();Printing the receipt...--------------------Done.repeat throws a RangeError for a negative length, but nobody asks you to catch it. The same goes for toInt.
So the responsibility is yours. Which errors a function throws is written in its documentation. In MDN, the JavaScript reference, that is the "Exceptions" section. Read it before you decide whether to catch.
The idea
finally: always
The till's printer has to be freed after every receipt, or the next one will not print. Whatever happens, success or error:
function printReceipt(qtyText) { console.log("The printer is busy."); try { const qty = toInt(qtyText); console.log(`Coffee: ${qty} x €2 = €${qty * 2}`); } catch (e) { console.log(`Wrong quantity: ${qtyText}`); } finally { console.log("The printer is free."); }}
printReceipt("3");printReceipt("three");The printer is busy.Coffee: 3 x €2 = €6The printer is free.The printer is busy.Wrong quantity: threeThe printer is free.finally runs always: when everything goes well, when the exception is caught, and even when there is no catch or it throws again. In that last case finally runs, and the exception keeps going up to the next try that can catch it.
Why not just a line after the try/catch? Because that line is skipped if the exception is not caught, or if try has a return. finally is not skipped.
More precisely, if you are curious
- You can also have
trywithfinallyand nocatch: the exception is not caught, but the cleanup runs.
- Java and Python have a shorter form that closes a file or a connection by itself. Node.js 22 has no such thing (
usingcomes in newer versions and gives aSyntaxErrorhere), so there the cleanup goes infinally.
finallydoes not run only if the whole program is stopped, for example withprocess.exit(0).
Try it
finally and return
What will the program print?
function reserve(guests) { try { if (guests > 6) { return `No table for ${guests}`; } return `Table for ${guests}`; } finally { console.log("Logged the attempt."); }}
console.log(reserve(8));A return in try does not skip finally. The function runs finally first, then returns the text. That is why "Logged the attempt." is printed before the result, which console.log gets only when the function is done.
Exercise
Programs with different errors
Solve these in your editor. Do not print prompts.
1. Price per person. The prices are in the array [120, 75, 300]. Read a product number (from 1) and the number of people, and print the price per person with integer division. Check for a product that does not exist (undefined) and for zero people with if, and catch wrong input and print its message.
| Input | Output |
|---|---|
| 2 and 3 | Per person: €25 |
| 5 and 2 | No such product. |
| 1 and 0 | The number of people cannot be 0. |
| 1 and two | Error: "two" is not a whole number. |
2. Calculator. Read a whole number, an operator (+, -, * or /) and a second whole number, each on its own line. Print the calculation (division is Math.trunc(a / b)). On text instead of a number say "Enter whole numbers.", on division by zero "Division by zero.", and on any other operator "Unknown operator: " followed by the operator. Division by zero here is an if.
84/484 / 4 = 2184/0Division by zero.More tasks
- The card terminal. The function
pay(amountText)prints "The terminal is connected.", then "Paid €X" or a message about wrong input, and infinally"The terminal is off." Call it with "25" and with "twenty". - Average grade without crashes. Read 5 lines with grades. Skip the ones that are not whole numbers (an exception) or are not from 2 to 6 (an
if). Print the average with integer division. If no grade is valid, print "No valid grades." (here division by zero does not throw, so it is anif). - Predict and check. Write a
trywithcatchandfinally, where each block prints a line. Predict the output on success, on a caught exception and on an uncaught one, then run it.
Test