So erstellen Sie benutzerdefinierte Fehler in JavaScript
Das Abfangen eines Fehlers ist unkompliziert. Zu wissen, welchen Fehler Sie abgefangen haben – und warum – ist der Punkt, an dem es kompliziert wird. Wenn Ihr catch-Block einen Datenbank-Timeout genauso behandelt wie ungültige Benutzereingaben, wird das Debugging zur Raterei.
Benutzerdefinierte JavaScript-Fehler lösen dieses Problem. Sie ermöglichen es Ihnen, strukturierte, identifizierbare Fehlertypen zu erstellen, die aussagekräftigen Kontext durch Ihre Anwendung transportieren. Dieser Leitfaden behandelt den modernen Ansatz unter Verwendung von ES2022+-Features, einschließlich Error.cause für Fehlerverkettung und JavaScript-Fehlerklassen, die zuverlässig in aktuellen Browsern und Laufzeitumgebungen funktionieren.
Wichtigste Erkenntnisse
- Erweitern Sie die native
Error-Klasse mit derclass ... extends Error-Syntax für saubere, wartbare benutzerdefinierte Fehler - Übergeben Sie sowohl
messageals auchoptionsansuper(), um Stack-Traces zu erhalten und Fehlerverkettung zu ermöglichen - Verwenden Sie
Error.cause(ES2022+), um ursprüngliche Fehler aus externen Quellen zu wrappen und zu bewahren - Fügen Sie strukturierte Felder wie
statusCodeoderfieldhinzu, anstatt Kontext in Message-Strings zu kodieren - Halten Sie Fehlerhierarchien flach – ein Basisfehler mit wenigen spezialisierten Untertypen deckt die meisten Anforderungen ab
Das Standardmuster: Error erweitern
Moderne Fehlerbehandlung in JavaScript beginnt mit der Klassensyntax. Vergessen Sie die veraltete Prototyp-Manipulation – class ... extends Error ist der grundlegende Ansatz:
class ValidationError extends Error {
constructor(message, options) {
super(message, options)
this.name = 'ValidationError'
}
}
Der super(message, options)-Aufruf ist entscheidend. Er übergibt sowohl die Nachricht als auch ein Options-Objekt an den übergeordneten Error-Konstruktor, der automatisch die Erfassung des Stack-Traces übernimmt.
Das Setzen von this.name stellt sicher, dass sich Ihr Fehler in Stack-Traces und Logging korrekt identifiziert. Ohne dies wird Ihr benutzerdefinierter Fehler als generischer “Error” angezeigt.
Strukturierte Felder hinzufügen
Einfache Message-Strings sind fragil. Das Parsen von “Invalid email: user@”, um Kontext zu extrahieren, ist fehleranfällig. Fügen Sie stattdessen strukturierte Felder hinzu:
class HttpError extends Error {
constructor(message, statusCode, options) {
super(message, options)
this.name = 'HttpError'
this.statusCode = statusCode
}
}
throw new HttpError('Resource not found', 404)
Jetzt kann Ihr Error-Handler direkt error.statusCode prüfen, anstatt Strings zu parsen. Dieses Muster skaliert sauber – fügen Sie details, code oder beliebige domänenspezifische Felder hinzu, die Ihre Anwendung benötigt.
Fehlerverkettung mit Error.cause
Beim Wrappen von Fehlern aus externen Quellen – Datenbanken, APIs, Bibliotheken – müssen Sie den ursprünglichen Fehler bewahren. Die cause-Option, eingeführt in ES2022, übernimmt dies:
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`)
if (!response.ok) {
throw new HttpError('User fetch failed', response.status)
}
return response.json()
} catch (error) {
throw new HttpError('Unable to load user', 500, { cause: error })
}
}
Der ursprüngliche Fehler bleibt über error.cause zugänglich und bewahrt die vollständige Fehlerkette für das Debugging. Dies wird in modernen Browsern und Node.js unterstützt – keine Polyfills erforderlich.
Discover how at OpenReplay.com.
Kleine Fehlerhierarchien aufbauen
JavaScript-Fehlerhierarchien funktionieren am besten, wenn sie flach gehalten werden. Ein Basis-Anwendungsfehler mit einigen wenigen spezialisierten Untertypen deckt die meisten Anforderungen ab:
class AppError extends Error {
constructor(message, options = {}) {
super(message, { cause: options.cause })
this.name = this.constructor.name
this.statusCode = options.statusCode ?? 500
}
}
class NotFoundError extends AppError {
constructor(message, options = {}) {
super(message, { ...options, statusCode: 404 })
}
}
class ValidationError extends AppError {
constructor(message, field, options = {}) {
super(message, { ...options, statusCode: 400 })
this.field = field
}
}
Die Verwendung von this.constructor.name setzt den Fehlernamen automatisch aus der Klasse und reduziert Boilerplate in Unterklassen.
Fehler in asynchronen Abläufen unterscheiden
Benutzerdefinierte Fehler glänzen in asynchronem Code, wo Sie verschiedene Fehlermodi behandeln müssen:
try {
const user = await fetchUser(userId)
} catch (error) {
if (error instanceof NotFoundError) {
return { status: 404, body: { message: 'User not found' } }
}
if (error instanceof ValidationError) {
return { status: 400, body: { message: error.message, field: error.field } }
}
// Unerwarteter Fehler – loggen und generische Antwort zurückgeben
console.error('Unexpected error:', error.cause ?? error)
return { status: 500, body: { message: 'Internal error' } }
}
Die instanceof-Prüfung funktioniert korrekt durch die Vererbungskette. Sie können auch nach benutzerdefinierten Feldern oder error.name unterscheiden, wenn Sie mit serialisierten Fehlern arbeiten.
Für Szenarien mit mehreren gleichzeitigen Fehlern bietet AggregateError eine Standardmethode, um Fehler zu bündeln – nützlich für parallele Operationen, bei denen mehrere unabhängig fehlschlagen können.
TypeScript-Überlegung
Wenn Sie TypeScript verwenden, setzen Sie target: "ES2022" oder höher, oder fügen Sie "ES2022" in Ihr lib-Array innerhalb von tsconfig.json ein. Dies stellt die korrekte Typisierung für die cause-Eigenschaft im ErrorOptions-Interface sicher.
Fazit
Benutzerdefinierte JavaScript-Fehler, die mit class ... extends Error erstellt wurden, geben Ihnen strukturierte, identifizierbare Exceptions, die Kontext durch Ihre Anwendung transportieren. Übergeben Sie message und options an super(), verwenden Sie cause für Fehlerverkettung und fügen Sie domänenspezifische Felder hinzu, anstatt Informationen in Message-Strings zu kodieren. Halten Sie Hierarchien flach, und Ihre Fehlerbehandlung wird vorhersehbar und debuggbar.
FAQs
Benutzerdefinierte Fehler ermöglichen es Ihnen, programmatisch zwischen verschiedenen Fehlertypen zu unterscheiden. Anstatt Fehlermeldungen zu parsen, um herauszufinden, was schiefgelaufen ist, können Sie instanceof-Prüfungen verwenden oder benutzerdefinierte Eigenschaften wie statusCode oder field inspizieren. Dies macht die Fehlerbehandlung zuverlässiger und Ihren Code einfacher zu debuggen und zu warten.
Error.cause wurde in ES2022 eingeführt und wird in allen modernen Browsern und Node.js 16.9+ unterstützt. Für ältere Umgebungen wird die cause-Option einfach ignoriert – Ihr Code funktioniert weiterhin, aber die cause-Eigenschaft wird undefined sein. Erwägen Sie ein Polyfill nur, wenn Sie Legacy-Laufzeitumgebungen unterstützen müssen.
Halten Sie sie flach – typischerweise maximal zwei Ebenen. Eine einzelne Basis-Anwendungsfehlerklasse mit einer Handvoll spezialisierter Untertypen deckt die meisten Anwendungsfälle ab. Tiefe Hierarchien fügen Komplexität hinzu, ohne proportionalen Nutzen, und erschweren das Refactoring, während sich Ihre Anwendung weiterentwickelt.
Ja. Wenn mehrere Operationen unabhängig fehlschlagen können, wrappen Sie einzelne Fehler in benutzerdefinierte Fehler und bündeln Sie sie dann mit AggregateError. Jeder Fehler im errors-Array behält seinen Typ, sodass Sie bei der Verarbeitung der Ergebnisse weiterhin instanceof-Prüfungen verwenden können.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.