try/catch ist ein Sicherheitsnetz, kein Sicherheitsgurt
Wenn eine Zeile JavaScript einen Fehler wirft, bleibt die Ausführung sofort stehen und der Fehler wandert den Call Stack nach oben. Fängt ihn niemand ab, stürzt das Programm ab (in Node) oder du bekommst eine rote Wand in der Konsole zu sehen (im Browser). Mit try/catch kannst du genau das abfangen — du sagst sozusagen: „Ich weiß, das hier kann schiefgehen, und so will ich damit umgehen."
Der grundlegende Aufbau sieht so aus:
JSON.parse wirft einen SyntaxError. Die Ausführung springt sofort in den catch-Block, wobei der Fehler an err gebunden wird. Das dritte console.log läuft trotzdem weiter — der Absturz wurde eingefangen.
Läuft der try-Block ohne Fehler durch, wird der catch-Block komplett übersprungen. Er ist ausschließlich für den Fehlerfall da.
Das Error-Objekt in JavaScript
Was auch immer geworfen wird, landet in dem Parameter, den du in catch (...) angibst. In der Regel ist das eine Instanz von Error mit drei nützlichen Feldern:
name verrät dir die Unterklasse (TypeError, RangeError, SyntaxError usw. – dazu mehr im nächsten Abschnitt). message enthält die lesbare Beschreibung. stack liefert den vollständigen Stacktrace – Gold wert beim Debuggen.
Ein Stolperstein: In JavaScript darfst du mit throw alles Mögliche werfen, nicht nur Error-Objekte. In altem Code sieht man deshalb manchmal Dinge wie throw "irgendwas ist kaputt". Wenn du selbst throw schreibst, wirf immer ein Error – nur so bekommen die Aufrufer einen Stacktrace:
finally läuft in jedem Fall
finally ist ein optionaler dritter Block, der ausgeführt wird – egal ob ein Fehler geworfen wurde oder nicht, und egal ob catch ihn behandelt hat. Er ist für Aufräumarbeiten gedacht: Dateien schließen, Locks freigeben, Ladespinner ausblenden:
Der Spinner wird ausgeblendet, egal ob das Laden geklappt hat oder fehlgeschlagen ist. Ohne finally müsstest du diese Zeile in beiden Zweigen schreiben — und in einem davon würdest du sie garantiert vergessen.
finally wird sogar dann ausgeführt, wenn im try- oder catch-Block ein return steht. Die Funktion kehrt erst nach finally zurück. Das überrascht manchmal, ist aber meistens genau das, was man will.
catch ist nicht immer nötig
catch ist optional. Ein reines try/finally ist völlig legitim und sinnvoll, wenn du zwar garantiert aufräumen willst, den Fehler aber gar nicht selbst behandeln möchtest — er soll einfach weiter nach oben durchgereicht werden:
Das innere try/finally gibt den Lock auch dann frei, wenn fn() eine Exception wirft – der Fehler wird aber nicht geschluckt, sondern landet beim Aufrufer. Fehler stillschweigend zu verschlucken ("hat nicht geklappt und keiner hat's mitbekommen") gehört zu den übelsten Debugging-Albträumen überhaupt.
Error rethrow in JavaScript: manche Fehler behandeln, den Rest weiterreichen
Ein catch-Block muss nicht zwangsläufig alles selbst abfangen. Du kannst den Fehler inspizieren, das behandeln, was du sinnvoll behandeln kannst, und den Rest per rethrow nach oben weitergeben:
Das instanceof-Muster ist hier der Schlüssel: Fang nur die Fehler ab, die du wirklich behandeln kannst, und lass alles andere nach oben durchreichen. Jeden Fehler stillschweigend mit einem leeren catch-Block zu schlucken, ist ein klassischer Code Smell — du verlierst damit jedes Signal, wenn etwas Unerwartetes schiefläuft.
try/catch mit async/await
In einer async-Funktion werden abgelehnte Promises beim await zu geworfenen Fehlern — und try/catch behandelt sie genauso wie synchrone Fehler:
Eine Feinheit dabei: Du musst das Promise innerhalb des try-Blocks awaiten. Gibst du ein Promise zurück, ohne es abzuwarten, tritt der Reject erst ein, nachdem die Funktion bereits verlassen wurde – und der catch-Block bekommt davon nichts mit:
async function bad() {
try {
return fetch("/broken"); // kein await — der Aufrufer sieht die Ablehnung
} catch (err) {
// wird nie ausgeführt
}
}
Faustregel: In async-Funktionen gehört ein await vor das, was dein try/catch abfangen soll.
Verschachteltes try/catch
try/catch-Blöcke lassen sich verschachteln, wenn innerer und äußerer Code aus unterschiedlichen Gründen fehlschlagen können und du beide Fälle getrennt behandeln willst:
Das innere catch kümmert sich um den Fall "Datenstruktur passt nicht" und liefert einen sicheren Default zurück. Das äußere catch übernimmt den Fall "Input war überhaupt kein JSON" und wirft den Fehler verpackt erneut. Verschachteltes try catch ist dann in Ordnung, wenn jede Ebene eine eigene Recovery-Strategie hat — wenn beide Blöcke dasselbe tun würden, zieh sie zu einem zusammen.
Wann try/catch verwenden – und wann nicht
try/catch ist ein Werkzeug für erwartete, behebbare Fehler. Es ist kein Mittel, um Bugs zu verstecken.
- Pack nicht den gesamten Funktionsrumpf "zur Sicherheit" ein. Wenn du keinen echten Plan für den Fehler hast, lass ihn nach oben durchschlagen — ein ungefangener Fehler mit Stacktrace ist deutlich hilfreicher als ein stillschweigend geschluckter.
- Nutz es nicht als Kontrollfluss.
try-Blöcke haben echten Overhead und machen den Code unleserlicher als eine einfacheif-Abfrage.if (user)schlägttry { user.name } catch {}um Längen. - Fang nicht nur ab, um zu loggen und dann zu ignorieren. Mindestens per
throwweiterreichen oder einen Sentinel-Wert zurückgeben, den der Aufrufer erkennen kann.
Der Denktest: "Was macht der Aufrufer dieses Codes, wenn das hier fehlschlägt?" Wenn du darauf keine Antwort hast, bist du noch nicht bereit, den Fehler per catch abzufangen.
Kurzreferenz zur Fehlerbehandlung
try { ... } catch (err) { ... }— geworfene Fehler abfangen.finally { ... }— läuft immer; ideal für Aufräumarbeiten.throw new Error("...")— wirf immerError-Subklassen, damit Stacktraces funktionieren.throw err;innerhalb voncatch— Rethrow, wenn du den Fehler nicht behandeln kannst.awaitinnerhalb vontry— nötig, damittry/catchasynchrone Rejections überhaupt sieht.
Weiter geht's: Error-Typen
TypeError, RangeError, SyntaxError — JavaScript bringt eine ganze Familie eingebauter Error-Klassen mit. Wer weiß, welche welchen Fall abdeckt, kann deutlich präziser fangen und berichten. Genau darum geht's im nächsten Kapitel.
Häufig gestellte Fragen
Wie funktioniert try/catch in JavaScript?
Du packst den riskanten Code in einen try { ... }-Block. Wirft irgendetwas darin einen Fehler, springt die Ausführung sofort in den catch (err) { ... }-Block – der geworfene Wert liegt dann in err. Läuft alles glatt, wird catch einfach übersprungen. Der optionale finally { ... }-Block läuft in jedem Fall und eignet sich gut für Aufräumarbeiten.
Wann sollte man try/catch in JavaScript überhaupt einsetzen?
Immer dann, wenn eine Operation zur Laufzeit realistisch schiefgehen kann: JSON.parse auf fremden Eingaben, fetch-Responses, Datei- oder Netzwerk-I/O. Aber nicht jede Zeile einwickeln – wenn du keinen Plan hast, wie du den Fehler behandelst, lass ihn besser nach oben durchreichen. Ein zu breiter try/catch um funktionierenden Code versteckt Bugs, statt sie zu lösen.
Fängt try/catch auch asynchrone Fehler ab?
Nur wenn du das Promise im try-Block mit await abwartest. Ein bloßer Aufruf wie somePromise() läuft daran vorbei – der Fehler landet als Unhandled Rejection. Mit async/await verhält sich try/catch dagegen genauso wie bei synchronem Code. Bei reinen Promise-Ketten nimmst du stattdessen .catch().
Wie werfe ich einen Fehler in JavaScript weiter (Rethrow)?
Im catch-Block reicht ein schlichtes throw err; – oder du wirfst einen neuen Error, der den ursprünglichen umschließt. Praktisch ist das, wenn du bestimmte Fehler selbst behandeln und den Rest nach oben durchreichen willst: Typ oder Message prüfen, das Behandelbare abfangen, alles andere erneut werfen, damit der aufrufende Code es weiterhin mitbekommt.