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 €.
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:
Wie viele Tickets? 12aGesamt: 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"));NaN1212JavaScript 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:
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:
Wie viele Tickets? 12afile:///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.2Dieser 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 mitnode:internalsind 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:
/projects/tickets/scores.js:6console.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.2Lies den Stacktrace. In welcher Zeile des Programms steckt der Fehler?
Wähle die Zeile mit dem Fehler.
scores[5] nicht. Es ergibt undefined, und die Funktion gibt das zurück. Das Programm hält erst in Zeile 6 an, wo .toFixed(1) auf undefined aufgerufen wird: TypeError: Cannot read properties of undefined. In JavaScript sagt dir der Stacktrace also, wo der Fehler sichtbar wurde, und manchmal liegt er weiter vorn: Woher kommt dieses undefined? Der letzte Index ist scores.length - 1.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!");12aWie viele Tickets? Das ist keine ganze Zahl.Vielen Dank!- In den Klammern von
catchsteht der Name der Exception, hiere. Eine Art schreibst du dort nicht:catchfängt in JavaScript jeden Fehler. Wie man die Arten unterscheidet, kommt in der nächsten Lektion. - Wenn im
tryalles gut geht, wirdcatchübersprungen. Mit der Eingabe12ist die Ausgabe „Gesamt: 180 €“ und „Vielen Dank!“. - Nach
catchmacht das Programm weiter. Es hält nicht mehr an. - Ab hier zeigen wir nur den Code zwischen
toIntundrl.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
- Das Programm fragt, wie viele Tickets. Du tippst „12a“.
- Im try: toInt versucht, aus „12a“ eine Zahl zu machen.
- Das geht nicht. Die Funktion wirft Error.
- Die Ausführung springt direkt in catch. Die Zeile mit „Gesamt“ wird übersprungen.
- catch fängt den Fehler (in JavaScript jeden Fehler): Es gibt die Meldung für den Kunden aus.
- Nach catch macht das Programm weiter. Es hält nicht an.
Genauer, für Neugierige
- Eine Variable, die im
trydeklariert wird (hiertickets), existiert nur bis zur schließenden Klammer destry. Wenn du sie auch nach dem Block brauchst, deklariere sie vor demtry.
- 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");„Ich lese“ wird ausgegeben, weil es vor dem Fehler steht. Dann wirft toInt, und die Ausführung springt direkt in catch. „Gelesen“ wird übersprungen. Nach catch macht das Programm mit „Ende“ weiter.
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}`);}12aFehler: "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);}TypeErrorCannot 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} €`);12azwölf12Wie 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
Numberwirft nie, deshalb prüft man in JavaScript üblicherweise mitif:Number.isInteger(Number(text)). Genau das machttoIntintern. 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);Infinity. Und 0 / 0 ergibt NaN. Selbst Math.trunc(90 / 0), die Ganzzahldivision aus der Lektion über Operatoren, ist wieder Infinity. Das Programm läuft mit einem falschen Wert weiter, ohne etwas zu sagen. Prüfe deshalb vor einer Division den Divisor mit if.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.
123.45123.abcDie Zahl ist: 123.45Fehler: 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 €.
7Es 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 €.
900Noch 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,3ist die AusgabeSumme: 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
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Parameter und return · Rekursion · Klassen und Objekte · Instanzmethoden
- Geht weiter in: try, catch und finally · Werfen und eigene Exceptions · Reguläre Ausdrücke · Dateien
- Aufgaben zum Thema: Fehler und Exceptions