JS
() =>
JavaScript · ES2022 · Fehlerbehandlung · Node.js
JavaScript error.cause: Fehlerketten aufbauen
und Original-Exceptions niemals verlieren

In JavaScript geht der ursprüngliche Fehler beim Re-Throw verloren – ein Error wird durch einen anderen ersetzt, der Kontext verschwindet. Das error.cause-Feature aus ES2022 löst genau dieses Problem: Es erlaubt strukturierte Fehlerketten, bei denen jeder Layer seinen Kontext beibehält und der Ursprungsfehler durchgereicht wird.

12 Min. Lesezeit error.cause · Error-Chaining · Custom Errors · Logging ES2022 · Node.js 18+ · Chrome 93+ · Firefox 91+

1. Das Problem: Fehlerkontexte gehen verloren

Jeder JavaScript-Entwickler kennt das Muster: Eine Funktion fängt eine Exception ab, will dem Aufrufer aber einen verständlicheren Fehler melden. Der naheliegende Ansatz ist throw new Error('Operation fehlgeschlagen') im catch-Block. Das Ergebnis: Der Original-Fehler – mit seinem Stack-Trace, seiner Meldung und seinem Typ – ist unwiederbringlich verloren. Im Production-Monitoring landet ein generischer "Operation fehlgeschlagen"-Fehler, ohne jeden Hinweis auf die eigentliche Ursache. Für Debugging ist das wertlos.

Vor ES2022 halfen sich Entwickler mit verschiedenen Workarounds: Den Originalfehler als Property auf dem neuen Error anhängen (newError.originalError = caughtError), ihn in die Error-Message einbetten (new Error('Operation fehlgeschlagen: ' + originalError.message)) oder komplexe Custom-Error-Klassen schreiben, die einen cause-Parameter im Konstruktor akzeptieren. Keiner dieser Ansätze war standardisiert, und kein Tool konnte darauf verlässlich aufbauen. JavaScript error.cause standardisiert dieses Muster als Teil von ES2022 – unterstützt von allen modernen Laufzeiten.

2. Syntax und Grundprinzip von error.cause

Die Syntax ist bewusst einfach: Der Error-Konstruktor akzeptiert als zweites Argument ein Options-Objekt mit einer cause-Property. Das Original-Error-Objekt (oder jeder andere Wert) wird darin übergeben und ist anschließend über error.cause zugänglich. Das funktioniert sowohl mit dem eingebauten Error-Typ als auch mit allen spezialisierten Built-in-Typen wie TypeError, RangeError, SyntaxError und eigenen Unterklassen. Die Property cause ist nicht auf Errors beschränkt – sie kann jeden Wert enthalten, aber in der Praxis ist das ursächliche Error-Objekt der sinnvollste Wert.

Das Grundprinzip von error.cause ist die Schaffung von Fehlerketten, die beim Traversieren vollständige Kontextinformationen liefern. Jede Ebene des Call-Stacks kann die Error-Meldung um ihren spezifischen Kontext anreichern und den Originalfehler unvermindert weitergeben. Das Ergebnis ist eine Kette aus Errors, bei der der innerste Fehler die technische Ursache trägt und jeder äußere Fehler den Businesskontext hinzufügt. Diese Struktur ist für Menschen lesbar und für automatische Analysetools durchsuchbar.


// Basic error.cause usage — ES2022 standard
async function loadUserProfile(userId) {
  let rawData;
  try {
    rawData = await fetchFromDatabase('users', userId);
  } catch (dbError) {
    // Wrap the database error with business context, preserve original
    throw new Error(`Failed to load user profile for ID ${userId}`, {
      cause: dbError  // original Error is preserved, accessible via .cause
    });
  }

  try {
    return JSON.parse(rawData);
  } catch (parseError) {
    throw new SyntaxError(`User profile data is malformed for ID ${userId}`, {
      cause: parseError
    });
  }
}

// Traverse the error chain
function getErrorChain(error) {
  const chain = [];
  let current = error;
  while (current) {
    chain.push({
      type: current.constructor.name,
      message: current.message,
      stack: current.stack
    });
    current = current.cause; // follow the chain
  }
  return chain;
}

// Usage
try {
  await loadUserProfile(42);
} catch (err) {
  console.log(getErrorChain(err));
  // [{type:'Error', message:'Failed to load user profile for ID 42', ...},
  //  {type:'DatabaseError', message:'Connection refused', ...}]
}

Eine wichtige Eigenschaft: error.cause ist eine gewöhnliche, enumerable Property. Das bedeutet, sie taucht in JSON.stringify-Ausgaben nicht automatisch auf (Error-Objekte werden standardmäßig als leere Objekte serialisiert), aber sie ist über error.cause direkt zugänglich. Für Logging-Systeme braucht man eine eigene Serialisierungsfunktion, die die cause-Kette explizit traversiert – ein Pattern, das in Abschnitt 5 detailliert beschrieben wird.

3. Custom Error-Klassen mit cause-Unterstützung

Custom Error-Klassen sind ein unverzichtbares Pattern in größeren JavaScript-Projekten. Sie erlauben typbasiertes Fehler-Handling mit instanceof und können anwendungsspezifische Properties wie HTTP-Status-Codes, Fehler-IDs oder Retry-Hinweise tragen. Mit error.cause lassen sich Custom Errors so gestalten, dass sie den cause-Parameter nahtlos an den Eltern-Konstruktor weiterleiten und damit vollständig in die standardisierte Fehlerkette integriert sind.

Das Muster für Custom Error-Klassen hat sich seit ES2022 leicht vereinfacht. Der Super-Aufruf mit dem Options-Objekt übergibt cause direkt an den eingebauten Error-Konstruktor. Eigene Properties werden im Konstruktor-Body hinzugefügt. Wichtig: Der name-Property muss explizit gesetzt werden, weil die eingebaute Error.prototype.name-Property sonst für alle Unterklassen "Error" bleibt, was instanceof korrekt funktionieren lässt, aber Stack-Traces und Logs unleserlich macht. Eine gut gestaltete Error-Hierarchie spiegelt die Schichten der Anwendung wider: Netzwerk-Errors, Datenbank-Errors, Validierungs-Errors und Business-Logic-Errors als separate Klassen, alle mit error.cause-Unterstützung.


// Custom error hierarchy with full error.cause support
class AppError extends Error {
  /**
   * Base application error with cause chaining support.
   * @param {string} message - Human-readable error description
   * @param {object} options - Error options including cause and metadata
   */
  constructor(message, { cause, code, retryable = false } = {}) {
    super(message, { cause }); // pass cause to built-in Error
    this.name = this.constructor.name; // 'AppError', 'NetworkError', etc.
    this.code = code;
    this.retryable = retryable;
    // Preserve stack across transpilers
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, this.constructor);
    }
  }
}

class NetworkError extends AppError {
  constructor(message, { cause, statusCode, url } = {}) {
    super(message, { cause, code: 'NETWORK_ERROR', retryable: statusCode >= 500 });
    this.statusCode = statusCode;
    this.url = url;
  }
}

class ValidationError extends AppError {
  constructor(message, { cause, field, value } = {}) {
    super(message, { cause, code: 'VALIDATION_ERROR', retryable: false });
    this.field = field;
    this.value = value;
  }
}

// Usage in a service layer
async function fetchProduct(id) {
  let response;
  try {
    response = await fetch(`/api/products/${id}`);
  } catch (fetchError) {
    throw new NetworkError(`Could not reach product API for ID ${id}`, {
      cause: fetchError,
      url: `/api/products/${id}`
    });
  }

  if (!response.ok) {
    throw new NetworkError(`Product API returned error for ID ${id}`, {
      statusCode: response.status,
      url: response.url
    });
  }
}

4. error.cause in asynchronem Code und Promises

Asynchroner Code in JavaScript verschärft das Problem des verlorenen Fehlerkontexts erheblich. Eine abgelehnte Promise trägt eine Error-Message, aber der Aufrufstack zum Zeitpunkt des reject()-Aufrufs ist oft wenig aussagekräftig – gerade in Callback-basierten APIs oder bei tief geschachtelten Promise-Ketten. error.cause hilft hier besonders: Beim Wrappen einer abgelehnten Promise im catch-Handler kann man den Original-Error als cause übergeben und gleichzeitig einen synchronen Stack-Frame mit aktuellem Kontext hinzufügen.

Mit async/await ist das Muster einfach zu implementieren: Jede async-Funktion, die Fehler an ihren Aufrufer weitergeben will, umschließt ihre await-Aufrufe in einem try/catch und wirft einen neuen Fehler mit dem originalen als cause. Das ergibt eine cause-Kette, die der Schachtelung der async-Funktionen entspricht. Ein besonders wertvolles Pattern bei API-Aufrufen: Netzwerkfehler, HTTP-Statusfehler und Parse-Fehler werden als separate Error-Typen mit jeweils eigenem cause geworfen, sodass der Aufrufer gezielt auf den Fehlertyp reagieren kann, während das Original für das Logging erhalten bleibt.

5. Strukturiertes Error-Logging mit Fehlerketten

Das volle Potenzial von error.cause entfaltet sich im Zusammenspiel mit strukturiertem Logging. Ein Error-Objekt kann nicht direkt in JSON serialisiert werden – JSON.stringify(new Error('test')) ergibt {}, weil Error-Properties nicht enumerable sind. Für ein Production-Logging-System braucht man eine Funktion, die ein Error-Objekt inklusive seiner cause-Kette in ein sauberes JSON-Objekt umwandelt. Dieses Objekt kann dann von Log-Aggregatoren wie Elasticsearch, Datadog oder Sentry verarbeitet werden, ohne Informationen zu verlieren.

Ein vollständiges Error-Logging-System serialisiert für jeden Fehler in der cause-Kette: Typ, Message, Stack-Trace und eigene Properties wie statusCode, code oder field. Das Ergebnis ist ein JSON-Array, das die vollständige Ursachenkette darstellt und in einem Log-Viewer navigiert werden kann. Error.cause macht damit Debugging in Production möglich, das vorher nur durch aufwändige Log-Korrelation oder Source-Code-Analyse erreichbar war. Sentry unterstützt cause-Ketten seit Version 7 direkt und stellt sie in der UI als "Chained Exception" dar.


// Serialize a full error.cause chain to a plain object for structured logging
function serializeError(error, depth = 0) {
  if (!error || depth > 10) return null; // prevent infinite loops

  const serialized = {
    type: error.constructor?.name ?? 'Unknown',
    message: error.message,
    stack: error.stack?.split('\n').slice(0, 8).join('\n'), // trim stack
  };

  // Capture custom properties (statusCode, code, field, etc.)
  const builtinKeys = new Set(['message', 'stack', 'name', 'cause']);
  for (const key of Object.getOwnPropertyNames(error)) {
    if (!builtinKeys.has(key)) {
      serialized[key] = error[key];
    }
  }

  if (error.cause) {
    serialized.cause = serializeError(error.cause, depth + 1);
  }

  return serialized;
}

// Logger wrapper for production use
function logError(logger, context, error) {
  logger.error({
    context,
    error: serializeError(error),
    timestamp: new Date().toISOString(),
    // Root cause extracted for quick filtering in log dashboards
    rootCause: getRootCause(error)?.message,
  });
}

function getRootCause(error) {
  let current = error;
  while (current.cause instanceof Error) {
    current = current.cause;
  }
  return current;
}

6. error.cause in Node.js: fetch, fs und Datenbankfehler

In Node.js begegnet man error.cause auch in den eingebauten APIs selbst. Die native fetch-Implementation in Node.js 18+ wirft bei Netzwerkproblemen Errors mit cause-Property, die auf den zugrundeliegenden Systemfehler oder den AbortSignal verweist. Das fs-Modul nutzt cause in bestimmten Fehlersituationen ebenfalls. Bekannte Datenbankbibliotheken wie Prisma und node-postgres unterstützen cause in neueren Versionen für datenbankspezifische Fehler.

Der praktische Nutzen in Node.js-Anwendungen ist besonders groß bei Service-to-Service-Kommunikation. Ein Request-Handler, der intern fünf Microservices aufruft, kann jeden externen Fehler mit dem cause-Pattern wrappen: Der HTTP-Handler wirft einen ServiceError mit dem Netzwerkfehler als cause, der Netzwerkfehler trägt den zugrundeliegenden ECONNREFUSED als cause. Im Application-Log landet eine vollständige Kette, die sofort zeigt: welcher Service nicht erreichbar war, welcher Code den Aufruf gemacht hat und was der Systemfehler war. Das ersetzt die häufige Situation, in der man aus einem generischen "Service unavailable"-Fehler manuell auf die eigentliche Ursache schließen muss.

7. Error-Chaining in Production-Monitoring-Tools

error.cause ist in modernen Error-Monitoring-Tools direkt integriert. Sentry erkennt die cause-Property automatisch und stellt die Fehlerkette als "Chained Exception" dar – jeder Fehler in der Kette erscheint als eigenes Panel mit seinem Stack-Trace. Das macht die Ursachenanalyse wesentlich schneller: Statt im Code zu suchen, welcher interne Fehler zu einem Frontend-Fehler geführt hat, sieht man die vollständige Kette direkt im Monitoring-Dashboard. Datadog APM unterstützt das gleiche Muster über strukturierte Log-Felder.

Das Design eines Error-Monitoring-Systems, das error.cause nutzt, folgt einem klaren Prinzip: Technische Fehler (Netzwerk, Datenbank, Parse-Fehler) werden nahe der Quelle gefangen, in einen Business-Fehler gewrapped und nach oben weitergegeben. Nur die äußersten Business-Fehler werden an das Monitoring gesendet – aber mit der vollständigen cause-Kette. Das verhindert Duplikate (der gleiche technische Fehler taucht nicht mehrfach auf) und liefert trotzdem vollständigen Kontext. Custom Error-Klassen mit eigenen Properties wie userId, requestId oder operationName reichern die Monitoring-Daten weiter an.

8. Antipatterns bei der Fehlerbehandlung ohne error.cause

Ohne error.cause greifen Entwickler zu Workarounds, die alle eigene Probleme mitbringen. Das häufigste Antipattern: den Originalfehler in die Message-String einbetten (new Error('Fehler: ' + originalError.message)). Das zerstört die Struktur: Der Typ des Originalfehlers ist verloren, sein Stack-Trace fehlt, und maschinelle Auswertung ist kaum möglich. Ein zweites verbreitetes Antipattern ist das stille Schlucken von Exceptions – catch (e) { /* ignore */ } – das besonders in Cleanup-Code vorkommt und systematisch Fehler versteckt, die für Debugging wertvoll wären.

Ein weiteres häufiges Problem: Fehler werden mehrfach geloggt. Jeder Layer loggt den Fehler, den er fängt, bevor er ihn weitergibt oder wrapped. Das Ergebnis sind Duplikate in den Logs: Der gleiche Fehler erscheint drei- oder viermal mit leicht unterschiedlichem Kontext, was die Analyse erschwert. Das korrekte Muster mit error.cause: Fehler werden nur am Eintrittspunkt geloggt (z.B. im Express-Error-Handler oder im Top-Level-Catch), aber mit der vollständigen cause-Kette. Dazwischenliegende Layer wrappen und werfen weiter, aber loggen nicht selbst.

9. Fehlerbehandlungs-Patterns im Vergleich

Die Evolution der JavaScript-Fehlerbehandlung zeigt deutlich, wie error.cause bestehende Patterns verbessert oder ersetzt.

Pattern Kontext erhalten Maschinell auswertbar Tool-Unterstützung
throw new Error(msg + orig.message) Nur Message-String Nein Kein
err.originalError = caught Ja, aber unstandardisiert Nur mit eigenem Code Kaum
Jeder Layer loggt selbst Ja, aber doppelt Schwierig (Duplikate) Standard-Logging
error.cause (ES2022) Vollständige Kette Ja, standardisiert Sentry, Node.js, Browser
Custom Error + cause-Hierarchie Kette + Business-Kontext Ja, typisiert Vollständig

Das Muster mit error.cause und Custom Error-Klassen ist heute die empfohlene Vorgehensweise für professionelle JavaScript-Anwendungen. Es löst die alten Probleme ohne Workarounds, ist von allen modernen Laufzeiten und Tools direkt unterstützt und verbessert die Debuggability von Production-Incidents erheblich. Der Aufwand für die Implementierung ist gering – es braucht eine kleine Serialisierungsfunktion, eine Error-Klassenhierarchie und konsequente Anwendung des Wrap-and-Throw-Patterns.

Mironsoft

JavaScript-Entwicklung, Fehlerbehandlung und Production-Monitoring

Fehlerbehandlung, die in Production wirklich hilft?

Wir implementieren strukturierte Fehlerbehandlung mit error.cause, Custom Error-Hierarchien und Integration in Sentry oder Datadog – damit Production-Incidents in Minuten statt Stunden analysiert werden können.

Error-Architektur

Custom Error-Hierarchie mit cause-Unterstützung und strukturierter Serialisierung

Monitoring-Integration

Sentry, Datadog oder eigener Log-Aggregator mit vollständigen Fehlerketten

Code-Review

Bestehenden Code auf Antipatterns prüfen und error.cause-Patterns einführen

10. Zusammenfassung

JavaScript error.cause ist eine kleine syntaktische Ergänzung mit großer Wirkung. Es standardisiert das Wrapping von Exceptions und macht Fehlerketten zu einem First-Class-Bürger in der Sprache. Jede Ebene einer Anwendung kann einen technischen Fehler mit Businesskontext anreichern, ohne den Original-Fehler zu verlieren. Custom Error-Klassen mit cause-Unterstützung ermöglichen typisiertes Fehler-Handling und maschinell auswertbare Fehlerstrukturen. Strukturiertes Logging, das die cause-Kette traversiert und serialisiert, macht Production-Debugging zu einem lösbaren Problem statt einem Ratespiel.

Die Unterstützung ist flächendeckend: alle modernen Browser, Node.js ab Version 16.9, und alle relevanten Error-Monitoring-Tools. Die Migration bestehenden Codes ist schrittweise möglich: Neue Fehler-Throw-Stellen nutzen error.cause, alte werden bei Gelegenheit nachgezogen. Das Ergebnis ist eine Codebasis, in der Production-Incidents nachvollziehbar sind und die Debugging-Zeit drastisch sinkt.

JavaScript error.cause — Das Wichtigste auf einen Blick

Syntax

throw new Error('Nachricht', { cause: originalError }) – das Original ist über error.cause zugänglich. Funktioniert mit allen Error-Typen.

Custom Errors

Custom-Klassen leiten cause im super()-Aufruf weiter. this.name explizit setzen. Eigene Properties für Business-Kontext hinzufügen.

Logging

cause-Kette manuell traversieren und serialisieren. Nur am Eintrittspunkt loggen, nicht in jedem Layer – verhindert Duplikate.

Tool-Support

Sentry stellt cause-Ketten als "Chained Exception" dar. Node.js 18+ nutzt cause in nativen APIs. Alle modernen Browser unterstützen ES2022.

11. FAQ: JavaScript error.cause

1Was ist error.cause?
Seit ES2022 standardisierte Property für Fehlerketten: new Error('msg', { cause: original }). Originalfehler bleibt vollständig erhalten und ist über error.cause zugänglich.
2Browser/Node.js-Unterstützung?
Chrome 93+, Firefox 91+, Safari 15+, Edge 93+, Node.js 16.9+. Flächendeckende Unterstützung in allen modernen Laufzeiten.
3Warum kein JSON.stringify für Errors?
Error-Properties sind nicht-enumerable. JSON.stringify gibt immer {} zurück. Eigene Serialisierungsfunktion mit Object.getOwnPropertyNames() und cause-Ketten-Traversierung erforderlich.
4error.cause vs. error.originalError?
originalError war unstandardisierter Workaround. error.cause ist ES2022-Standard, von Laufzeiten und Tools wie Sentry direkt unterstützt.
5Jeden Layer loggen?
Nein – das erzeugt Duplikate. Nur am Eintrittspunkt loggen, mit vollständiger cause-Kette. Zwischenlayer wrappen und weiterwerfen.
6Kann cause einen Nicht-Error-Wert enthalten?
Ja, jeder Wert ist erlaubt. In der Praxis ist das ursächliche Error-Objekt sinnvoll, weil es Stack-Trace und Message mitbringt.
7Sentry-Unterstützung?
Ab Sentry Version 7 automatisch erkannt. Darstellung als "Chained Exception" mit eigenem Panel pro Fehler in der Kette.
8Custom Error-Hierarchie aufbauen?
AppError extends Error, cause im super()-Aufruf weitergeben, this.name auf Klassenname setzen. Spezialisierungen wie NetworkError erben und fügen eigene Properties hinzu.
9getRootCause implementieren?
while-Schleife traversiert die cause-Kette bis kein weiterer cause instanceof Error vorhanden ist. Der innerste Fehler ist die technische Ursache.
10Immer error.cause im catch-Block nutzen?
Überall wo ein Fehler gewrapped und weitergeworfen wird. Originalfehler niemals stillschweigend wegwerfen – das ist das häufigste Antipattern.