try, catch und finally
Kurz gesagt: In JavaScript gibt es ein einziges catch: Darin prüfst du die Art des Fehlers mit instanceof, und finally läuft immer, mit oder ohne Fehler.
Problem
Die Rechnung für null Personen
Das Programm teilt die Restaurantrechnung gleichmäßig auf. Falsche Eingaben kennst du schon, und du fängst sie:
try { const bill = toInt(await ask("")); const people = toInt(await ask("")); console.log(`Pro Person: ${Math.trunc(bill / people)} €`);} catch (e) { console.log("Gib ganze Zahlen ein.");}Die Rechnung beträgt 90 €, und jemand gibt 0 als Zahl der Personen ein:
Pro Person: Infinity €Das Programm hält nicht an, aber es gibt Unsinn aus. In JavaScript wirft die Division durch null nicht. Sie ergibt Infinity, also hat catch nichts zu fangen. Und wenn die Fehler wirklich von verschiedener Art sind, gibt es trotzdem nur ein catch. Wie sagst du dem Kunden dann genau, was nicht stimmt?
Die Idee
Ein catch, mehrere Arten
Die Null ist keine Exception, also ist sie ein if:
try { const bill = toInt(await ask("")); const people = toInt(await ask("")); if (people === 0) { console.log("Es können nicht 0 Personen sein."); } else { console.log(`Pro Person: ${Math.trunc(bill / people)} €`); }} catch (e) { console.log("Gib ganze Zahlen ein.");}| Eingabe | Ausgabe |
|---|---|
| 90 und 3 | Pro Person: 30 € |
| 90 und 0 | Es können nicht 0 Personen sein. |
| 90 und drei | Gib ganze Zahlen ein. |
Aber es gibt Fehler, die JavaScript selbst wirft. Der Kassenbon hat eine Linie aus Strichen, und ihre Länge wird eingegeben. "-".repeat(-1) wirft einen RangeError, und toInt wirft einen Error. Zwei Arten, aber nach try gibt es nur ein catch. Die Art prüfst du darin mit instanceof („ist von der Art“):
const text = await ask("");try { const width = toInt(text); console.log("-".repeat(width));} catch (e) { if (e instanceof RangeError) { console.log("Die Länge darf nicht negativ sein."); } else { console.log("Gib eine ganze Zahl ein."); }}| Eingabe | Ausgabe |
|---|---|
| 5 | ----- |
| -1 | Die Länge darf nicht negativ sein. |
| fünf | Gib eine ganze Zahl ein. |
Die Prüfungen gehen von oben nach unten, wie bei jedem if / else if. Der erste Zweig, dessen Art passt, läuft, und nur dieser.
const text = await ask("");try { const width = toInt(text); console.log("-".repeat(width));} catch (e) { if (e instanceof RangeError) { console.log("Die Länge darf nicht negativ sein."); } else { console.log("Gib eine ganze Zahl ein."); }}Schritt für Schritt, in Worten
- Erster Lauf: Als Länge der Linie wird „fünf“ eingegeben.
- toInt kann aus „fünf“ keine Zahl machen: Ein Error wird geworfen.
- Die erste Prüfung ist für RangeError. Sie passt nicht und wird übersprungen.
- Das else passt. Nur dieses läuft.
- Zweiter Lauf: „-1“ wird eingegeben.
- toInt ergibt -1, und "-".repeat(-1) wirft einen RangeError.
- Schon die erste Prüfung passt. Das else wird nie erreicht.
- Die Prüfungen gehen von oben nach unten. Die erste, die passt, läuft.
Genauer, für Neugierige
- Was du hier nicht fangen willst, wirfst du im
elseerneut:throw e;. Wiethrowfunktioniert, kommt in der nächsten Lektion.
Die Idee
Die Familie der Exceptions
Fehler sind Klassen, die durch Vererbung verbunden sind. Hier sind die häufigsten:
Error├── TypeError falsche Art von Wert: undefined.seats├── RangeError außerhalb des Erlaubten: "-".repeat(-1)├── SyntaxError falsche Schreibweise: JSON.parse("{")├── ReferenceError ein Name, den es nicht gibt├── URIError├── EvalError└── AggregateErrorinstanceof prüft die Art, die du angibst, und alle ihre Unterklassen. Das ist die Polymorphie aus dem Modul über Vererbung: Ein Objekt einer Unterklasse ist auch von der Art der Basisklasse.
const error = new RangeError("Invalid count value: -1");console.log(error instanceof RangeError);console.log(error instanceof Error);console.log(error instanceof TypeError);truetruefalsecatch fängt alles, und e.name sagt dir die Art:
function printLine(widthText) { try { console.log("-".repeat(toInt(widthText))); } catch (e) { console.log(`${e.name}: ${e.message}`); }}
printLine("5");printLine("-1");printLine("fünf");-----RangeError: Invalid count value: -1Error: "fünf" ist keine ganze Zahl.e.name ist der Name der Art des Fehlers: RangeError von repeat, Error von toInt.
Genauer, für Neugierige
- In JavaScript kann jeder Wert geworfen werden, sogar ein String, deshalb ist
eimcatchnicht immer einError. Das siehst du in der nächsten Lektion.
- Fange so speziell, wie du kannst. Ein catch, das die Art nicht prüft, versteckt auch Fehler, die du nicht erwartet hast, etwa einen Bug in deinem eigenen Code.
Probier es aus
Welches catch?
Was gibt das Programm aus?
const tables = [{ seats: 4 }, { seats: 2 }, { seats: 6 }];try { console.log(`Tisch 4: ${tables[3].seats} Plätze`);} catch (e) { if (e instanceof RangeError) { console.log("Außerhalb des Erlaubten"); } else if (e instanceof TypeError) { console.log("Falscher Wert"); } else { console.log("Allgemeiner Fehler"); }}tables[3] wirft nicht. Es ist undefined. Der Fehler kommt bei .seats: Von undefined kann man nicht lesen, und das ist ein TypeError. Er ist kein RangeError, deshalb geht die erste Prüfung nicht durch. Die zweite geht durch und läuft. Das else wird nie erreicht, obwohl es ihn auch fangen würde.Probier es aus
Finde den Fehler
text = "-1" gibt das Programm „Etwas ist schiefgegangen.“ aus und nicht „Die Länge darf nicht negativ sein.“. In welcher Zeile steckt der Fehler?Wähle die Zeile mit dem Fehler.
RangeError ist eine Unterklasse von Error, deshalb ist e instanceof Error auch für ihn wahr. Die erste Prüfung fängt alles, und die zweite kann nie erreicht werden. JavaScript sagt nichts: Das Programm läuft und gibt einfach die allgemeine Meldung aus. Die Regel lautet vom Speziellen zum Allgemeinen: Vertausche die beiden Prüfungen.Die Idee
Niemand zwingt dich
Java hat Checked Exceptions: Der Compiler akzeptiert das Programm erst, wenn du sie fängst oder mit throws deklarierst. JavaScript hat keine solchen. Kein Fehler muss gefangen werden:
function printReceipt() { console.log("Drucke den Kassenbon..."); console.log("-".repeat(20)); console.log("Fertig.");}
printReceipt();Drucke den Kassenbon...--------------------Fertig.repeat wirft bei einer negativen Länge einen RangeError, aber niemand verlangt, dass du ihn fängst. Dasselbe gilt für toInt.
Die Verantwortung liegt also bei dir. Welche Fehler eine Funktion wirft, steht in ihrer Dokumentation. In MDN, der Referenz für JavaScript, ist das der Abschnitt „Exceptions“. Lies ihn, bevor du entscheidest, ob du fängst.
Die Idee
finally: immer
Der Drucker an der Kasse muss nach jedem Kassenbon freigegeben werden, sonst wird der nächste nicht gedruckt. Egal, was passiert, Erfolg oder Fehler:
function printReceipt(qtyText) { console.log("Der Drucker ist belegt."); try { const qty = toInt(qtyText); console.log(`Kaffee: ${qty} x 2 € = ${qty * 2} €`); } catch (e) { console.log(`Falsche Menge: ${qtyText}`); } finally { console.log("Der Drucker ist frei."); }}
printReceipt("3");printReceipt("drei");Der Drucker ist belegt.Kaffee: 3 x 2 € = 6 €Der Drucker ist frei.Der Drucker ist belegt.Falsche Menge: dreiDer Drucker ist frei.finally läuft immer: wenn alles gut geht, wenn die Exception gefangen wird, und sogar wenn es kein catch gibt oder es erneut wirft. Im letzten Fall läuft finally, und die Exception geht weiter nach oben zum nächsten try, das sie fangen kann.
Warum nicht einfach eine Zeile nach dem try/catch? Weil diese Zeile übersprungen wird, wenn die Exception nicht gefangen wird, oder wenn im try ein return steht. finally wird nicht übersprungen.
Genauer, für Neugierige
- Es geht auch
trymitfinallyund ohnecatch: Die Exception wird nicht gefangen, aber das Aufräumen läuft.
- Java und Python haben eine kürzere Form, die eine Datei oder eine Verbindung von selbst schließt. Node.js 22 hat so etwas nicht (
usingkommt in neueren Versionen und ergibt hier einenSyntaxError), deshalb gehört das Aufräumen dort infinally.
finallyläuft nur dann nicht, wenn das ganze Programm angehalten wird, etwa mitprocess.exit(0).
Probier es aus
finally und return
Was gibt das Programm aus?
function reserve(guests) { try { if (guests > 6) { return `Kein Tisch für ${guests}`; } return `Tisch für ${guests}`; } finally { console.log("Versuch protokolliert."); }}
console.log(reserve(8));Ein return im try überspringt finally nicht. Die Funktion führt zuerst finally aus und gibt dann den Text zurück. Deshalb wird „Versuch protokolliert.“ vor dem Ergebnis ausgegeben, das console.log erst bekommt, wenn die Funktion fertig ist.
Übung
Programme mit verschiedenen Fehlern
Löse die Aufgaben in deinem Editor. Gib keine Eingabeaufforderungen aus.
1. Preis pro Person. Die Preise stehen im Array [120, 75, 300]. Lies eine Produktnummer (ab 1) und die Zahl der Personen und gib den Preis pro Person mit Ganzzahldivision aus. Prüfe ein Produkt, das es nicht gibt (undefined), und null Personen mit if, und fange falsche Eingaben und gib ihre Meldung aus.
| Eingabe | Ausgabe |
|---|---|
| 2 und 3 | Pro Person: 25 € |
| 5 und 2 | Dieses Produkt gibt es nicht. |
| 1 und 0 | Die Zahl der Personen darf nicht 0 sein. |
| 1 und zwei | Fehler: "zwei" ist keine ganze Zahl. |
2. Taschenrechner. Lies eine ganze Zahl, einen Operator (+, -, * oder /) und eine zweite ganze Zahl, jede in einer eigenen Zeile. Gib die Rechnung aus (die Division ist Math.trunc(a / b)). Bei Text statt einer Zahl sag „Gib ganze Zahlen ein.“, bei Division durch null „Division durch null.“ und bei jedem anderen Operator „Unbekannter Operator: “ und dahinter den Operator. Die Division durch null ist hier ein if.
84/484 / 4 = 2184/0Division durch null.Weitere Aufgaben
- Das Kartenterminal. Die Funktion
pay(amountText)gibt „Das Terminal ist verbunden.“ aus, dann „Bezahlt: X €“ oder eine Meldung über die falsche Eingabe, und imfinally„Das Terminal ist aus.“ Rufe sie mit „25“ und mit „zwanzig“ auf. - Durchschnittsnote ohne Absturz. Lies 5 Zeilen mit Noten. Überspringe die, die keine ganzen Zahlen sind (eine Exception) oder nicht zwischen 2 und 6 liegen (ein
if). Gib den Durchschnitt mit Ganzzahldivision aus. Wenn keine Note gültig ist, gib „Keine gültigen Noten.“ aus (hier wirft die Division durch null nicht, also ist es einif). - Vorhersagen und prüfen. Schreibe ein
trymitcatchundfinally, in dem jeder Block eine Zeile ausgibt. Sage die Ausgabe bei Erfolg, bei einer gefangenen und bei einer nicht gefangenen Exception voraus, und führe es dann aus.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Instanzmethoden · Vererbung · Polymorphie · Exceptions
- Geht weiter in: Werfen und eigene Exceptions · Dateien · Daten in Dateien · Hashcodes und Hashfunktionen
- Aufgaben zum Thema: Fehler und Exceptions