Fehler behandeln
Programme scheitern an Eingaben, die nicht passen, an Servern, die nicht antworten, und an Daten, die anders aussehen als erwartet. In Teil 1 und Teil 7 lädst du Daten von einem Server und musst dort mit solchen Fehlern umgehen. Diese Einheit zeigt die Grundlagen.
Lernziele
Nach dieser Einheit kannst du
- einen Fehler mit
throwauslösen und eine verständliche Meldung mitgeben, - Fehler mit
tryundcatchabfangen und erklären, wannfinallyläuft, - die häufigsten Fehlertypen von JavaScript erkennen,
- eine eigene Fehlerklasse schreiben und Fehler gezielt behandeln.
Fehler auslösen und abfangen
Abschnitt betitelt „Fehler auslösen und abfangen“throw löst einen Fehler aus. Das Programm bricht an dieser Stelle ab und springt zum nächsten passenden catch. Gibt es keines, endet das ganze Programm mit der Meldung des Fehlers.
function parseDeposit(text) { const value = Number(text) if (Number.isNaN(value) || value < 0) { throw new RangeError(`Ungültiges Pfand ${text}`) } return value}
for (const text of ['20', '-5', 'zwanzig']) { try { console.log(parseDeposit(text)) } catch (error) { console.log(error.name, error.message) } finally { console.log('geprüft', text) }}parseDeposit wirft einen RangeError, wenn der Text keine gültige Zahl ist oder die Zahl negativ wäre. Die Schleife ruft die Funktion in einem try-Block auf. Bei '20' läuft alles normal. Bei '-5' und 'zwanzig' springt das Programm in den catch-Block, der Name und Meldung des Fehlers ausgibt. Der finally-Block läuft in jedem Fall, mit oder ohne Fehler. Er eignet sich zum Aufräumen, etwa um eine Ladeanzeige auszublenden.
Fehlertypen
Abschnitt betitelt „Fehlertypen“JavaScript kennt mehrere eingebaute Fehlertypen. Die meisten wirft die Sprache selbst.
| Typ | Wann er auftritt | Beispiel |
|---|---|---|
TypeError | ein Wert hat nicht den erwarteten Typ | undefined.name |
ReferenceError | eine Variable gibt es nicht | ein Tippfehler im Namen einer Variablen |
SyntaxError | Code oder JSON ist nicht gültig | JSON.parse("{ 'name': 'Zelt' }") |
RangeError | ein Wert liegt ausserhalb des erlaubten Bereichs | (20).toFixed(200) |
Error | allgemeiner Fehler für eigene Fälle | throw new Error('Server nicht erreichbar') |
Jeder Fehler hat ein Feld name mit dem Typ und ein Feld message mit der Meldung. In der Konsole siehst du ausserdem den Stack, die Kette der Aufrufe bis zu der Stelle, an der der Fehler auftrat.
Eigene Fehlerklassen
Abschnitt betitelt „Eigene Fehlerklassen“Für Fehler, die zur eigenen App gehören, lohnt sich eine eigene Klasse. Sie erbt von Error und kann zusätzliche Felder tragen.
class NotAvailableError extends Error { constructor(item) { super(`${item} ist ausgeliehen`) this.name = 'NotAvailableError' this.item = item }}
function reserve(item, available) { if (!available) throw new NotAvailableError(item) return `${item} reserviert`}
try { console.log(reserve('Bohrmaschine', true)) console.log(reserve('Zelt', false))} catch (error) { if (error instanceof NotAvailableError) console.log(error.message) else throw error}NotAvailableError ruft mit super den Konstruktor von Error auf, setzt einen eigenen Namen und merkt sich das Ding. Der catch-Block prüft mit instanceof, ob es dieser Fehler ist. Andere Fehler wirft er mit throw error weiter, denn an dieser Stelle weiss das Programm nicht, wie es mit ihnen umgehen soll. Die Reservation der Bohrmaschine klappt, beim Zelt springt das Programm in den catch-Block.
Typische Fehler
Abschnitt betitelt „Typische Fehler“| Fehler | Was hilft |
|---|---|
Ein leerer catch-Block verschluckt jeden Fehler, und niemand merkt etwas. | Den Fehler mindestens mit console.error ausgeben oder weiterwerfen. |
catch behandelt alle Fehler gleich, auch Programmierfehler wie einen TypeError. | Mit instanceof prüfen und unbekannte Fehler weiterwerfen. |
throw 'Ungültig' wirft einen Text statt eines Fehlers, und error.message ist undefined. | Immer ein Fehlerobjekt werfen, throw new Error('Ungültig'). |
Code nach einem return im try-Block soll aufräumen und läuft nicht. | Aufräumen in finally schreiben, das läuft auch nach einem return. |
Ein KI-Assistent packt jeden Aufruf in try und catch und gibt nur «Fehler» aus. | Fehler dort behandeln, wo du etwas Sinnvolles tun kannst, und eine Meldung zeigen, die sagt, was passiert ist. |
Aufgaben
Abschnitt betitelt „Aufgaben“Aufgabe Einstieg
Schreib eine Funktion parseDays(text), die eine Anzahl Tage aus einem Formularfeld liest. Erlaubt sind ganze Zahlen von 1 bis 7, sonst wirft die Funktion einen RangeError mit einer Meldung, die sagt, was erlaubt ist. Teste mit '3', '9' und '2.5' und gib bei einem Fehler die Meldung aus.
Lösung
function parseDays(text) { const days = Number(text) if (!Number.isInteger(days) || days < 1 || days > 7) { throw new RangeError(`Eine Ausleihe dauert 1 bis 7 Tage, nicht ${text}`) } return days}
for (const text of ['3', '9', '2.5']) { try { console.log(parseDays(text)) } catch (error) { console.log(error.message) }}Number.isInteger lehnt 2.5 ab und auch NaN, falls jemand Text eingibt. Die Meldung nennt den erlaubten Bereich und den falschen Wert, so weiss das Mitglied, was es ändern muss.
Aufgabe Vertiefung
Die Einstellungen der App liegen als JSON-Text vor. Schreib eine Funktion readSettings(text), die den Text mit JSON.parse liest. Ist der Text kein gültiges JSON, gibt sie eine Meldung in der Konsole aus und liefert die Standardeinstellung { pageSize: 20 }.
Lösung
function readSettings(text) { try { return JSON.parse(text) } catch (error) { console.log('Einstellungen unlesbar, Standard wird verwendet', error.name) return { pageSize: 20 } }}
console.log(readSettings('{"pageSize": 50}').pageSize)console.log(readSettings('pageSize: 50').pageSize)Die Ausgabe ist 50, danach die Meldung mit SyntaxError und 20. Der zweite Text ist kein JSON, weil der Schlüssel in doppelten Anführungszeichen und das Ganze in geschweiften Klammern stehen müsste. Das return im catch-Block sorgt dafür, dass die App mit brauchbaren Werten weiterläuft.
Aufgabe Knobelei
Läuft finally auch dann, wenn der catch-Block die Funktion mit return verlässt? Schreib eine Funktion load, die einen Fehler wirft, ihn abfängt und im catch-Block 'fallback' zurückgibt. Halte in einem Array fest, welche Blöcke in welcher Reihenfolge laufen.
Lösung
const steps = []
function load() { try { steps.push('try') throw new Error('Server nicht erreichbar') } catch (error) { steps.push('catch') return 'fallback' } finally { steps.push('finally') }}
console.log(load(), steps.join(' > '))Die Ausgabe ist fallback try > catch > finally. finally läuft, nachdem catch den Rückgabewert bestimmt hat, aber bevor die Funktion wirklich zurückkehrt. Darum eignet sich finally zum Aufräumen, egal auf welchem Weg eine Funktion endet.
Texte und Abbildungen stehen unter CC BY-SA 4.0, der Code unter der MIT-Lizenz und die Daten des Kurses unter CC0 1.0. Die Schrift der Abbildungen ist Kalam unter der SIL Open Font License 1.1. Die Quellen liegen auf GitHub.