Zum Inhalt springen

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 throw auslösen und eine verständliche Meldung mitgeben,
  • Fehler mit try und catch abfangen und erklären, wann finally läuft,
  • die häufigsten Fehlertypen von JavaScript erkennen,
  • eine eigene Fehlerklasse schreiben und Fehler gezielt behandeln.

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.

parse.mjs
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.

JavaScript kennt mehrere eingebaute Fehlertypen. Die meisten wirft die Sprache selbst.

TypWann er auftrittBeispiel
TypeErrorein Wert hat nicht den erwarteten Typundefined.name
ReferenceErroreine Variable gibt es nichtein Tippfehler im Namen einer Variablen
SyntaxErrorCode oder JSON ist nicht gültigJSON.parse("{ 'name': 'Zelt' }")
RangeErrorein Wert liegt ausserhalb des erlaubten Bereichs(20).toFixed(200)
Errorallgemeiner Fehler für eigene Fällethrow 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.

Für Fehler, die zur eigenen App gehören, lohnt sich eine eigene Klasse. Sie erbt von Error und kann zusätzliche Felder tragen.

custom.mjs
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.

FehlerWas 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.

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
solution-1.mjs
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
solution-2.mjs
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
solution-3.mjs
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.