Einleitung: Warum Fehlerbehandlung das Fundament zuverlässigen Codes ist
Fehler im Code sind unvermeidlich. Sie entstehen durch fehlerhafte Benutzereingaben, Netzwerkausfälle, fehlende Dateien auf dem Server oder banale Tippfehler des Entwicklers. In der Node.js-Umgebung, in der Code asynchron ausgeführt wird und häufig mit externen Ressourcen (Datenbanken, Dateisystem, API) interagiert, kann ein unbehandelter Fehler zum Absturz des gesamten Prozesses führen. Eine kompetente Fehlerbehandlung in JavaScript verwandelt eine fragile Anwendung in einen stabilen Dienst. In diesem Artikel werden wir alle wichtigen Mechanismen durchgehen: vom klassischen try/catch bis zur Erstellung eigener Fehlerklassen und der Arbeit mit asynchronem Code.
1. Grundlagen: try, catch, finally und throw
Der grundlegende Block zur Behandlung von Ausnahmen in JavaScript ist die try...catch...finally-Konstruktion. Sie ermöglicht es, einen Fehler, der in einem "gefährlichen" Codeabschnitt auftritt, abzufangen und korrekt darauf zu reagieren, ohne die Ausführung des gesamten Programms zu unterbrechen.
Syntax und Funktionsweise von try/catch
Code, der eine Ausnahme auslösen könnte, wird in den try-Block gesetzt. Tritt ein Fehler auf, wird die Kontrolle sofort an den catch-Block übergeben, in dem das Fehlerobjekt verfügbar ist. Tritt kein Fehler auf, wird der catch-Block ignoriert.
try { const data = JSON.parse('{"name": "Anna"}'); // Korrektes JSON console.log(data.name); // Gibt aus: Anna} catch (error) { console.error('Parsing-Fehler:', error.message);}// Die Ausführung wird fortgesetztJetzt simulieren wir einen Fehler:
try { const data = JSON.parse('ungültiges json'); // Fehler! console.log(data.name);} catch (error) { console.error('Parsing-Fehler:', error.message); // Ausgabe: Parsing-Fehler: Unexpected token 'ü', ... is not valid JSON}console.log('Das Programm lebt!'); // Dieser Code wird ausgeführtDer finally-Block: Code, der immer ausgeführt wird
Der finally-Block wird unabhängig davon ausgeführt, ob ein Fehler aufgetreten ist oder nicht. Er eignet sich ideal zum Freigeben von Ressourcen (Schließen von Dateien, Datenbankverbindungen) oder zum Bereinigen von Timern.
let resource = null;try { resource = acquireResource(); // Ressource holen resource.doSomething(); // Gefährliche Operation} catch (error) { console.error('Fehler:', error);} finally { if (resource) { resource.close(); // Ressource immer schließen console.log('Ressource freigegeben.'); }}Der throw-Operator: Eigene Fehler erstellen
Sie können selbst einen Fehler mit throw "werfen". Dies ist nützlich für die Validierung von Eingabedaten oder zum Unterbrechen der Ausführung bei Verletzung der Geschäftslogik. Als Argument kann alles übergeben werden, aber die beste Praxis ist die Verwendung eines Error-Objekts oder seiner Unterklassen.
function divide(a, b) { if (typeof a !== 'number' || typeof b !== 'number') { throw new Error('Argumente müssen Zahlen sein'); } if (b === 0) { throw new Error('Division durch Null ist verboten'); } return a / b;}
try { console.log(divide(10, 0));} catch (error) { console.error('Fehler:', error.message); // Fehler: Division durch Null ist verboten}2. Fortgeschrittene Techniken: Eigene Fehlerklassen und Typisierung
In großen Projekten reicht die Verwendung des standardmäßigen Error nicht aus. Man muss zwischen Netzwerkfehlern, Validierungsfehlern, Autorisierungsfehlern und anderen unterscheiden. Dazu erstellt man benutzerdefinierte Klassen, die den integrierten Error erweitern.
Erstellung benutzerdefinierter Fehler
class ValidationError extends Error { constructor(message) { super(message); this.name = 'ValidationError'; // Eindeutiger Name zur Überprüfung }}
class NetworkError extends Error { constructor(message, statusCode) { super(message); this.name = 'NetworkError'; this.statusCode = statusCode; }}
function fetchUser(id) { if (!id) { throw new Valida