Zum Inhalt
wannacode

Exceptions

Kurz gesagt: Eine Exception ist ein Objekt, das einen Fehler beschreibt, der während des Programmlaufs passiert ist: Der Stacktrace zeigt, wo, und try und catch fangen ihn, damit das Programm weiterlaufen kann.

Problem

12a statt 12

Die Konzertkasse fragt, wie viele Tickets du willst. Ein Ticket kostet 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("Wie viele Tickets? "));
console.log(`Gesamt: ${tickets * 15} €`);
rl.close();

Die ersten acht Zeilen sind ask() aus der Lektion über die Eingabe. Darunter steht das eigentliche Programm.

Mit der Eingabe 12 sagt das Programm „Gesamt: 180 €“. Aber der Kunde hat es eilig und tippt einen Buchstaben zu viel:

Die Konsole
Wie viele Tickets? 12a
Gesamt: NaN €

Das Programm hält nicht an, aber richtig ist es auch nicht: „NaN €“ ist keine Summe. Schlimmer noch, es läuft weiter, als wäre alles in Ordnung. Wie kann das Programm merken, dass die Eingabe falsch ist, und was soll es dann tun?

Die Idee

Exception

Number muss eine Zahl zurückgeben. Aus „12a“ kann es keine Zahl machen, und es wirft auch keinen Fehler. Es gibt den besonderen Wert NaN („keine Zahl“) zurück. NaN * 15 ist wieder NaN. Und parseInt ist noch stiller: Es liest „12“ und überspringt den Rest.

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

JavaScript läuft oft mit einem falschen Wert weiter, statt anzuhalten. Die Prüfung ist also deine Sache. Hier steckt sie in der fertigen Funktion toInt (Zeilen 10 bis 16). Kopiere sie vorerst; wie man sie schreibt, lernst du in der nächsten Lektion:

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}" ist keine ganze Zahl.`);
}
return number;
}
const tickets = toInt(await ask("Wie viele Tickets? "));
console.log(`Gesamt: ${tickets * 15} €`);
rl.close();

Wenn der Text keine ganze Zahl ist, erzeugt toInt ein Objekt, das das Problem beschreibt, und wirft es. Dieses Objekt heißt Exception (Ausnahme, in JavaScript einfach error). Wenn niemand sie fängt, hält das Programm an:

Die Konsole
Wie viele Tickets? 12a
file:///projects/tickets/tickets.js:13
throw new Error(`"${text}" ist keine ganze Zahl.`);
^
Error: "12a" ist keine ganze Zahl.
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

Dieser Text heißt Stacktrace, und du liest ihn von oben nach unten:

  • Ganz oben: die Datei und die Zeile, in der die Exception geworfen wurde, die Zeile selbst und ein ^ unter der Stelle.
  • Die Zeile nach der leeren: die Art und die Meldung. Error: "12a" ist keine ganze Zahl.
  • Die Zeilen mit „at“: wo es passiert ist. Das ist der Aufrufstapel aus der Lektion über Rekursion, von innen nach außen. Ganz oben ist toInt, das geworfen hat, und darunter die Stelle, von der es aufgerufen wurde: tickets.js:18. Die Zahl danach ist die Spalte. Die Zeilen mit node:internal sind im Code von Node.js, und du brauchst sie selten.

Die Meldungen der Sprache selbst sind auf Englisch, weil die Sprache sie schreibt, nicht du.

Probier es aus

Finde den Fehler

Das Programm soll das letzte Ergebnis aus der Klassenarbeit ausgeben. Es hält damit an:

Fehler
/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

Lies den Stacktrace. In welcher Zeile des Programms steckt der Fehler?

Wähle die Zeile mit dem Fehler.

Die Idee

try und catch

Eine Exception kann man fangen. Der Code, der werfen könnte, kommt in try („versuche“). Was bei einem Fehler passieren soll, schreibst du in catch („fange“):

const text = await ask("Wie viele Tickets? ");
try {
const tickets = toInt(text);
console.log(`Gesamt: ${tickets * 15} €`);
} catch (e) {
console.log("Das ist keine ganze Zahl.");
}
console.log("Vielen Dank!");
Eingabe
12a
Ausgabe
Wie viele Tickets? Das ist keine ganze Zahl.
Vielen Dank!
  • In den Klammern von catch steht der Name der Exception, hier e. Eine Art schreibst du dort nicht: catch fängt in JavaScript jeden Fehler. Wie man die Arten unterscheidet, kommt in der nächsten Lektion.
  • Wenn im try alles gut geht, wird catch übersprungen. Mit der Eingabe 12 ist die Ausgabe „Gesamt: 180 €“ und „Vielen Dank!“.
  • Nach catch macht das Programm weiter. Es hält nicht mehr an.
  • Ab hier zeigen wir nur den Code zwischen toInt und rl.close().

const text = await ask("Wie viele Tickets? ");
try {
const tickets = toInt(text);
console.log(`Gesamt: ${tickets * 15} €`);
} catch (e) {
console.log("Das ist keine ganze Zahl.");
}
console.log("Vielen Dank!");
Schritt für Schritt, in Worten
  1. Das Programm fragt, wie viele Tickets. Du tippst „12a“.
  2. Im try: toInt versucht, aus „12a“ eine Zahl zu machen.
  3. Das geht nicht. Die Funktion wirft Error.
  4. Die Ausführung springt direkt in catch. Die Zeile mit „Gesamt“ wird übersprungen.
  5. catch fängt den Fehler (in JavaScript jeden Fehler): Es gibt die Meldung für den Kunden aus.
  6. Nach catch macht das Programm weiter. Es hält nicht an.
Genauer, für Neugierige
  • Eine Variable, die im try deklariert wird (hier tickets), existiert nur bis zur schließenden Klammer des try. Wenn du sie auch nach dem Block brauchst, deklariere sie vor dem try.
  • Der Name ist optional: catch { … } ohne Klammern funktioniert auch, wenn du den Fehler nicht brauchst.

Probier es aus

Welche Zeilen laufen?

Was gibt das Programm aus?

console.log("Start");
try {
console.log("Ich lese");
const seats = toInt("zehn");
console.log(`Gelesen: ${seats}`);
} catch (e) {
console.log("Falsche Eingabe");
}
console.log("Ende");
Antwort

Die Idee

Was in der Exception steht

Die Exception ist ein Objekt, und e in catch (e) ist die Variable, die darauf zeigt. Die Meldung steht in e.message.

try {
const tickets = toInt(await ask(""));
console.log(`Gesamt: ${tickets * 15} €`);
} catch (e) {
console.log(`Fehler: ${e.message}`);
}
Eingabe
12a
Ausgabe
Fehler: "12a" ist keine ganze Zahl.

Hier ist die Meldung auf Deutsch, weil toInt sie schreibt. Die Fehler der Sprache selbst kommen mit eigenen Meldungen auf Englisch, und ihre Art steht 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);
}
Ausgabe
TypeError
Cannot read properties of undefined (reading 'toFixed')

Die Meldung ist nützlich für dich, wenn du den Fehler suchst. Dem Kunden zeigst du meistens deinen eigenen Text in seiner Sprache, etwa „Das ist keine ganze Zahl.“

Die Idee

Frag, bis es stimmt

Der Kunde hat sich vertippt. Du musst nicht anhalten. Frag noch einmal:

let tickets;
while (true) {
const text = await ask("Wie viele Tickets? ");
try {
tickets = toInt(text);
break;
} catch (e) {
console.log("Gib eine ganze Zahl ein, zum Beispiel 3.");
}
}
console.log(`Gesamt: ${tickets * 15} €`);
Eingabe
12a
zwölf
12
Ausgabe
Wie viele Tickets? Gib eine ganze Zahl ein, zum Beispiel 3.
Wie viele Tickets? Gib eine ganze Zahl ein, zum Beispiel 3.
Wie viele Tickets? Gesamt: 180 €

while (true) läuft immer weiter, bis break die Schleife beendet. Und break wird nur erreicht, wenn toInt Erfolg hat. Bei einem Fehler springt die Ausführung in catch, danach beginnt die Schleife von vorn und fragt noch einmal.

Genauer, für Neugierige
  • Number wirft nie, deshalb prüft man in JavaScript üblicherweise mit if: Number.isInteger(Number(text)). Genau das macht toInt intern. Eine Exception ist praktisch, wenn die Prüfung tief in einer Funktion steckt und der Fehler die Stelle erreichen muss, die weiß, was zu tun ist.

Probier es aus

Division durch null

Nicht jede Exception kommt von der Eingabe. Der durchschnittliche Bestellwert ist der Umsatz geteilt durch die Zahl der Bestellungen. Aber am ersten Tag gibt es keine Bestellungen. Was passiert, wenn du durch null teilst?

Was gibt das Programm aus?

const revenue = 90;
const orders = 0;
const average = revenue / orders;
console.log(average);
Antwort
Genauer, für Neugierige
  • In Java und C# haben ganze Zahlen keinen Wert für unendlich, deshalb bleibt dort nur die Exception. Für Kommazahlen kommen die Regeln aus dem Standard IEEE 754 für Gleitkommazahlen, dem alle Sprachen folgen.
  • In JavaScript sind alle Zahlen number, Kommazahlen nach IEEE 754, deshalb wirft eine Division nie eine Exception.

Übung

Programme, die nicht anhalten

Löse die Aufgaben in deinem Editor. Gib keine Eingabeaufforderungen aus, damit deine Ausgabe mit dem Beispiel übereinstimmt.

1. Text zu Zahl. Schreibe eine Funktion toNumber(text) nach dem Vorbild von toInt: Sie gibt Number(text) zurück, und wenn das Ergebnis NaN ist (prüfe mit Number.isNaN) oder der Text leer ist, wirft sie. Lies zwei Zeilen. Gib für jede die Zahl aus oder eine Meldung, dass es keine Zahl ist. Fange die Exception dort, wo du die Funktion aufrufst.

Eingabe
123.45
123.abc
Ausgabe
Die Zahl ist: 123.45
Fehler: 123.abc ist keine Zahl.

2. Kein solches Produkt. Die Preise stehen im Array [120, 75, 300, 45, 89]. Lies eine Produktnummer von 1 bis 5 und gib ihren Preis aus. Wenn die Nummer außerhalb des Arrays liegt, sag es. Hier gibt es keine Exception: prices[6] ist undefined, also prüfe mit if. Mit der Eingabe 2 ist die Ausgabe Preis: 75 €.

Eingabe
7
Ausgabe
Es gibt kein Produkt mit der Nummer 7.

3. Durchschnittliche Bestellung. Lies den Umsatz und die Zahl der Bestellungen (ganze Zahlen) und gib den Durchschnitt mit Ganzzahldivision aus. Wenn es keine Bestellungen gibt, sag es. Auch hier gibt es keine Exception (Infinity), also prüfe mit if. Mit der Eingabe 900 und 4 ist die Ausgabe Durchschnitt: 225 €.

Eingabe
90
0
Ausgabe
Noch keine Bestellungen.

Weitere Aufgaben

  • Alter, bis es stimmt. Frage nach dem Alter, bis die Eingabe eine ganze Zahl von 0 bis 120 ist. Bei Text wie „zwanzig“ sag „Das ist keine Zahl.“, und bei 150 sag „Das Alter muss zwischen 0 und 120 liegen.“ Das eine ist eine Exception, das andere ein if.
  • Summe der gültigen. Lies 5 Zeilen. Addiere die, die ganze Zahlen sind, und zähle die übersprungenen: Mit der Eingabe 10, abc, 5, 7.5, 3 ist die Ausgabe Summe: 18, übersprungen: 2.
  • Division auf Wunsch. Lies zwei ganze Zahlen und gib den Quotienten aus. Bei Text statt einer Zahl und bei Division durch null hält das Programm nicht an. Es fragt noch einmal nach beiden Zahlen. Die Division durch null ist hier ein if.

Test

Teste dich

1. Was ist eine Exception?
2. Was ergibt Number("12a")?
3. In einem try stehen vier Zeilen. Die zweite wirft eine Exception, und catch fängt sie. Was passiert mit der dritten und der vierten?
4. Was ergibt 90 / orders, wenn orders 0 ist?
5. Was ergibt scores[10] in einem Array mit 5 Elementen?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·