Fortgeschrittene Fehlerbehandlung in TypeScript: Von try/catch zu typsicheren Lösungen
Fehlerbehandlung ist ein kritischer Aspekt der Entwicklung mit TypeScript. Das grundlegende try/catch reicht für komplexe Anwendungen oft nicht aus. In diesem Artikel betrachten wir fortgeschrittene Techniken, die helfen, den Code zuverlässiger, vorhersagbarer und typsicherer zu machen.
1. Das Problem des standardmäßigen try/catch: Typverlust
Der Hauptnachteil des klassischen catch ist, dass die Fehlervariable den Typ unknown (oder any in älteren Versionen) hat. Dies zwingt den Entwickler, den Typ manuell zu überprüfen:
try { riskyOperation();} catch (error) { // error: unknown if (error instanceof Error) { console.error(error.message); } else { console.error('Unbekannter Fehler', error); }}Lösung: Erstellung eines Hilfsprogramms für typisierten Fehlerabfang:
function getErrorMessage(error: unknown): string { if (error instanceof Error) return error.message; if (typeof error === 'string') return error; if (error && typeof error === 'object' && 'message' in error) { return String((error as { message: unknown }).message); } return 'Ein unbekannter Fehler ist aufgetreten';}
try { riskyOperation();} catch (error) { console.error(getErrorMessage(error));}2. Benutzerdefinierte Fehlerklassen mit typisierten Daten
Die Erstellung einer Fehlerhierarchie ermöglicht es, das Problem genau zu identifizieren und zusätzliche Daten zu übergeben:
// Basisklasseclass AppError extends Error { constructor( message: string, public readonly code: number, public readonly isOperational: boolean = true ) { super(message); this.name = 'AppError'; }}
// Spezialisierte Fehlerclass ValidationError extends AppError { constructor( message: string, public readonly field: string, public readonly invalidValue: unknown ) { super(message, 400); this.name = 'ValidationError'; }}
class NotFoundError extends AppError { constructor(entity: string, id: string) { super(`${entity} mit ID ${id} nicht gefunden`, 404, false); this.name = 'NotFoundError'; }}
// Verwendungfunction findUser(id: string): User { if (!validateId(id)) { throw new ValidationError('Ungültige ID', 'id', id); } const user = db.findUser(id); if (!user) { throw new NotFoundError('Benutzer', id); } return user;}
try { const user = findUser('123');} catch (error) { if (error instanceof NotFoundError) { // error.code === 404, error.message enthält Beschreibung console.warn(error.message); } else if (error instanceof ValidationError) { // error.field und error.invalidValue sind verfügbar console.error(`Fehler im Feld ${error.field}: ${error.message}`); } else if (error instanceof AppError) { console.error(`Anwendungsfehler (Code ${error.code}): ${error.message}`); }}3. Discriminated Unions für die Fehlerbehandlung
Dieses Muster ermöglicht die Verarbeitung von Operationsergebnissen ohne Ausnahmen, indem eine Vereinigung von Typen mit einem Diskriminanten verwendet wird:
// Ergebnistyp mit Diskriminanttype Result<T, E = Error> = | { success: true; data: T } | { success: false; error: E };
// Funktion, die ein Result zurückgibtfunction safeDivide(a: number, b: number): Result<number, string> { if (b === 0) { return { success: false, error: 'Division durch Null' }; } return { success: true, data: a / b };}
// Verwendungconst result = safeDivide(10, 0);
if (result.success) { // TypeScript weiß, dass result.data — number ist console.log(`Ergebnis: ${result.data}`);} else { // result.error — string console.error(`Fehler: ${result.error}`);}
// Komplexeres Beispiel mit benutzerdefinierten Fehlerntype UserResult = Result<