Skip to content
wannacode

When something goes wrong

Problem

12a instead of 12

The concert box office asks how many tickets you want. A ticket is €15.

tickets.js
import { createInterface } from "node:readline";
const rl = createInterface({ input: process.stdin });
const lines = rl[Symbol.asyncIterator]();
async function ask(question) {
process.stdout.write(question);
return (await lines.next()).value;
}
const tickets = Number(await ask("How many tickets? "));
console.log(`Total: €${tickets * 15}`);
rl.close();

The first eight lines are ask() from the lesson on input. Below them is the program itself.

With the input 12 the program says "Total: €180". But the customer is in a hurry and hits one letter too many:

The console
How many tickets? 12a
Total: €NaN

The program does not stop, but it is not right either: "€NaN" is not a total. Worse, it goes on as if everything were fine. How can the program tell that the input is wrong, and what should it do then?

The idea

Exception

Number has to return a number. It cannot make a number out of "12a", and it does not throw an error. It returns the special value NaN ("not a number"). NaN * 15 is still NaN. And parseInt is even quieter: it reads "12" and skips the rest.

console.log(Number("12a"));
console.log(Number("12"));
console.log(parseInt("12a"));
Output
NaN
12
12

JavaScript often goes on with a wrong value instead of stopping. So the checking is up to you. Here it is in the ready-made function toInt (lines 10 to 16). For now, copy it; you will learn how to write it in the next lesson:

tickets.js
import { createInterface } from "node:readline";
const rl = createInterface({ input: process.stdin });
const lines = rl[Symbol.asyncIterator]();
async function ask(question) {
process.stdout.write(question);
return (await lines.next()).value;
}
function toInt(text) {
const number = Number(text);
if (text.trim() === "" || !Number.isInteger(number)) {
throw new Error(`"${text}" is not a whole number.`);
}
return number;
}
const tickets = toInt(await ask("How many tickets? "));
console.log(`Total: €${tickets * 15}`);
rl.close();

When the text is not a whole number, toInt creates an object that describes the problem and throws it. This object is called an exception (in JavaScript, simply an error). If nobody catches it, the program stops:

The console
How many tickets? 12a
file:///projects/tickets/tickets.js:13
throw new Error(`"${text}" is not a whole number.`);
^
Error: "12a" is not a whole number.
at toInt (file:///projects/tickets/tickets.js:13:11)
at file:///projects/tickets/tickets.js:18:17
at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
Node.js v22.22.2

This text is called a stack trace, and you read it from top to bottom:

  • At the very top: the file and the line where the exception was thrown, the line itself, and a ^ under the spot.
  • The line after the empty one: the kind and the message. Error: "12a" is not a whole number.
  • The lines with "at": where it happened. This is the call stack from the recursion lesson, from the inside out. At the top is toInt, which threw, and under it is the place it was called from: tickets.js:18. The number after that is the column. The lines with node:internal are in the code of Node.js, and you rarely need them.

The messages of the language itself are in English because the language writes them, not you.

Try it

Find the bug

The program should print the last score from the test. It stops with this:

Error
/projects/tickets/scores.js:6
console.log(lastScore(scores).toFixed(1));
^
TypeError: Cannot read properties of undefined (reading 'toFixed')
at Object.<anonymous> (/projects/tickets/scores.js:6:30)
at Module._compile (node:internal/modules/cjs/loader:1705:14)
at Object..js (node:internal/modules/cjs/loader:1838:10)
at Module.load (node:internal/modules/cjs/loader:1441:32)
at Function._load (node:internal/modules/cjs/loader:1263:12)
at TracingChannel.traceSync (node:diagnostics_channel:328:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:237:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
at node:internal/main/run_main_module:36:49
Node.js v22.22.2

Read the stack trace. Which line of the program is the bug on?

Pick the line with the bug.

The idea

try and catch

An exception can be caught. The code that might throw goes in try. What should happen on an error, you write in catch:

const text = await ask("How many tickets? ");
try {
const tickets = toInt(text);
console.log(`Total: €${tickets * 15}`);
} catch (e) {
console.log("That is not a whole number.");
}
console.log("Thank you!");
Input
12a
Output
How many tickets? That is not a whole number.
Thank you!
  • In the parentheses of catch is the name of the exception, here e. You do not write a kind there: catch in JavaScript catches every error. How to tell the kinds apart is in the next lesson.
  • If everything in try goes well, catch is skipped. With the input 12 the output is "Total: €180" and "Thank you!".
  • After catch the program continues on. It no longer stops.
  • From here on we show only the code between toInt and rl.close().

const text = await ask("How many tickets? ");
try {
const tickets = toInt(text);
console.log(`Total: €${tickets * 15}`);
} catch (e) {
console.log("That is not a whole number.");
}
console.log("Thank you!");
Step by step, in words
  1. The program asks how many tickets. You type "12a".
  2. In try: toInt tries to make a number out of "12a".
  3. It cannot. The function throws Error.
  4. Execution jumps straight into catch. The "Total" line is skipped.
  5. catch catches the error (in JavaScript, any error): it prints the message for the customer.
  6. After catch the program continues. It does not stop.
More precisely, if you are curious
  • A variable declared in try (here tickets) exists only until the closing brace of try. If you need it after the block too, declare it before try.
  • The name is optional: catch { … } without parentheses works too, when you do not need the error.

Try it

Which lines run?

What will the program print?

console.log("Start");
try {
console.log("Reading");
const seats = toInt("ten");
console.log(`Got ${seats}`);
} catch (e) {
console.log("Bad input");
}
console.log("End");
Answer

The idea

What the exception says

The exception is an object, and e in catch (e) is the variable that points to it. The message is in e.message.

try {
const tickets = toInt(await ask(""));
console.log(`Total: €${tickets * 15}`);
} catch (e) {
console.log(`Error: ${e.message}`);
}
Input
12a
Output
Error: "12a" is not a whole number.

Here the message is the one toInt writes. The errors of the language itself come with their own messages in English, and their kind is in e.name:

try {
const scores = [62, 85, 47, 91, 78];
console.log(scores[5].toFixed(1));
} catch (e) {
console.log(e.name);
console.log(e.message);
}
Output
TypeError
Cannot read properties of undefined (reading 'toFixed')

The message is useful to you when you are looking for the bug. To the customer you usually show your own text, in their language, such as "That is not a whole number."

The idea

Ask until it is right

The customer made a mistake. You do not have to stop. Ask again:

let tickets;
while (true) {
const text = await ask("How many tickets? ");
try {
tickets = toInt(text);
break;
} catch (e) {
console.log("Enter a whole number, for example 3.");
}
}
console.log(`Total: €${tickets * 15}`);
Input
12a
twelve
12
Output
How many tickets? Enter a whole number, for example 3.
How many tickets? Enter a whole number, for example 3.
How many tickets? Total: €180

while (true) keeps looping until break stops it. And break is reached only if toInt succeeds. On an error, execution jumps into catch, and after it the loop starts over and asks again.

More precisely, if you are curious
  • Number never throws, so in JavaScript the usual check is with if: Number.isInteger(Number(text)). That is exactly what toInt does inside. An exception is handy when the check is deep inside a function and the error has to reach the place that knows what to do.

Try it

Division by zero

Not every exception comes from the input. The average order value is the revenue divided by the number of orders. But on the first day there are no orders. What happens when you divide by zero?

What will the program print?

const revenue = 90;
const orders = 0;
const average = revenue / orders;
console.log(average);
Answer
More precisely, if you are curious
  • In Java and C#, whole numbers have no value for infinity, so all that is left there is the exception. For decimal numbers, the rules come from the IEEE 754 standard for floating point numbers, which every language follows.
  • In JavaScript all numbers are number, decimal by IEEE 754, so division never throws an exception.

Exercise

Programs that do not stop

Solve these in your editor. Do not print prompts, so your output matches the sample.

1. Text to number. Write a function toNumber(text) modeled on toInt: it returns Number(text), and when the result is NaN (check with Number.isNaN) or the text is empty, it throws. Read two lines. For each, print the number or a message that it is not a number. Catch the exception where you call the function.

Input
123.45
123.abc
Output
The number is: 123.45
Error: 123.abc is not a number.

2. No such product. The prices are in the array [120, 75, 300, 45, 89]. Read a product number from 1 to 5 and print its price. If the number is outside the array, say so. There is no exception here: prices[6] is undefined, so check with if. With the input 2 the output is Price: €75.

Input
7
Output
There is no product number 7.

3. Average order. Read the revenue and the number of orders (whole numbers) and print the average with integer division. If there are no orders, say so. There is no exception here either (Infinity), so check with if. With the input 900 and 4 the output is Average: €225.

Input
90
0
Output
No orders yet.

More tasks

  • Age, until it is right. Ask for an age until the input is a whole number from 0 to 120. For text like "twenty" say "That is not a number.", and for 150 say "The age must be from 0 to 120." One is an exception, the other is an if.
  • Sum of the valid ones. Read 5 lines. Add up the ones that are whole numbers and count the skipped ones: with the input 10, abc, 5, 7.5, 3 the output is Sum: 18, skipped: 2.
  • Division on demand. Read two whole numbers and print the quotient. On text instead of a number and on division by zero, the program does not stop. It asks again for both numbers. Division by zero here is an if.

Test

Check yourself

1. What is an exception?
2. What does Number("12a") give?
3. A try has four lines. The second one throws an exception, and catch catches it. What happens to the third and the fourth?
4. What does 90 / orders give when orders is 0?
5. What does scores[10] give in an array of 5 elements?

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