Skip to content
wannacode

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:

bill.js
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:

Output
Per person: €Infinity

The 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
  1. First run: "five" is typed in for the length of the line.
  2. toInt cannot make a number out of "five": an Error is thrown.
  3. The first check is for RangeError. It does not match, so it is skipped.
  4. The else matches. It runs, and only it.
  5. Second run: "-1" is typed in.
  6. toInt gives -1, and "-".repeat(-1) throws a RangeError.
  7. The very first check matches. The else is never reached.
  8. 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;. How throw works is in the next lesson.

The idea

The family of exceptions

Errors are classes, related by inheritance. Here are the most common ones:

The hierarchy
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
└── AggregateError

instanceof 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);
Output
true
true
false

catch 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");
Output
-----
RangeError: Invalid count value: -1
Error: "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 catch the e is not always an Error. 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");
}
}
Answer

Try it

Find the bug

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

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();
Output
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");
Output
The printer is busy.
Coffee: 3 x €2 = €6
The printer is free.
The printer is busy.
Wrong quantity: three
The 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 try with finally and no catch: 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 (using comes in newer versions and gives a SyntaxError here), so there the cleanup goes in finally.
  • finally does not run only if the whole program is stopped, for example with process.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));
Answer

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.

Input
84
/
4
Output
84 / 4 = 21
Input
84
/
0
Output
Division 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 in finally "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 an if).
  • Predict and check. Write a try with catch and finally, where each block prints a line. Predict the output on success, on a caught exception and on an uncaught one, then run it.

Test

Check yourself

1. The catch has an if / else if with instanceof. The exception matches two of the checks. Which one runs?
2. e is a RangeError. What does e instanceof Error give?
3. Why does the check e instanceof Error have to come last?
4. When does finally run?
5. How many catch blocks can a single try have in JavaScript?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·