Zum Inhalt springen
aviral gupta

// I4.2 · ca. 34 Min. · Aufbau

Eigene Fehler und error.cause

Nach dieser Lektion geben Sie Ihrem Programm eigene Fehlertypen, entscheiden nach Typ statt nach Meldungstext und behalten den ursprünglichen Fehler, wenn Sie Kontext hinzufügen.

Lektion 2 von 5 in I4 Fehler, Debugging und Tests

Danach können Sie

  • Eine Error-Klasse mit extends Error, eigenem name und Zusatzfeldern definieren
  • Fehler mit instanceof und ihren Feldern unterscheiden, nicht durch Auswerten der Meldung
  • Einen Fehler einer tieferen Ebene mit new Error(message, {cause}) umhüllen und error.cause lesen
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen, aus Lektion I4.1: ein eingebauter Fehlertyp. Was gibt dieser Code aus?

    const list = [];
    try {
      list.length = -1;
    } catch (error) {
      console.log(error.name);
    }
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: Die Klasse erweitert nur Error. Was gibt dieser Code aus?

    class NotFoundError extends Error {}
    
    const error = new NotFoundError("No user with id 2");
    console.log(error.name, error instanceof NotFoundError);
  3. Üben · Aufgabe 3 von 7

    Ergänzen Sie das Argument, das die Ursache an Error weiterreicht.

    super(message, ____);
    super(message, );
  4. Üben · Aufgabe 4 von 7

    Zwei Ebenen von Fehlerklassen. Was gibt dieser Code aus?

    class AppError extends Error {}
    class DbError extends AppError {}
    
    const error = new DbError("Connection lost");
    console.log(error instanceof AppError, error instanceof TypeError);
  5. Üben · Aufgabe 5 von 7

    Was gibt dieser Code aus?

    const low = new Error("ECONNREFUSED");
    const high = new Error("Could not load users", {cause: low});
    console.log(high.message + " / " + high.cause.message);
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Der Fehler wird in JSON umgewandelt, zum Beispiel zum Loggen. Was gibt dieser Code aus?

    class ValidationError extends Error {
      constructor(field, message) {
        super(message);
        this.name = "ValidationError";
        this.field = field;
      }
    }
    
    console.log(JSON.stringify(new ValidationError("email", "Email is missing")));
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe: Schreiben Sie OutOfStockError (extends Error, name, ein Feld item, options an super weitergereicht) und order(item), das ihn wirft, wenn der Bestand 0 ist, und einen gewöhnlichen Error für einen unbekannten Artikel. checkout(items) bestellt jeden Artikel und umhüllt jeden Fehler mit new Error("Checkout failed", {cause}). Probieren Sie ["tea"], ["tea", "milk"] und ["cake"] aus und geben Sie die Ursache aus: den Artikel, wenn er ausverkauft ist, sonst ihre Meldung.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Zwei Fehlerklassen und ein umhüllter Fehler

NotFoundError trägt die id, die nicht gefunden wurde, und die Schleife behandelt nur diesen Typ, jeder andere Fehler würde das Programm also weiterhin stoppen. ValidationError zeigt das vollständige Muster: options an super weitergereicht, ein name und ein Feld. parseSettings umhüllt den SyntaxError von JSON.parse mit einem Fehler, der sagt, was das Programm gerade tat.

main.js

class ValidationError extends Error {
  constructor(field, message, options) {
    super(message, options); // first: sets message, and cause if given
    this.name = "ValidationError";
    this.field = field;
  }
}

class NotFoundError extends Error {
  constructor(id) {
    super(`No user with id ${id}`);
    this.name = "NotFoundError";
    this.id = id;
  }
}

const users = new Map([[1, {name: "Ada"}]]);

function findUser(id) {
  const user = users.get(id);
  if (!user) throw new NotFoundError(id);
  return user;
}

function parseSettings(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    throw new Error("Could not read the settings", {cause: error});
  }
}

for (const id of [1, 2]) {
  try {
    console.log(findUser(id).name);
  } catch (error) {
    if (error instanceof NotFoundError) console.log("404:", error.message);
    else throw error;
  }
}

const invalid = new ValidationError("email", "Email is missing");
console.log(String(invalid), invalid.field, invalid instanceof Error);

try {
  parseSettings("{oops");
} catch (error) {
  console.log(error.message, "<-", error.cause.name);
}

Ausführen mit

node main.js

Ausgabe

Ada
404: No user with id 2
ValidationError: Email is missing email true
Could not read the settings <- SyntaxError
  • String(error) ist name, ein Doppelpunkt und message; ein gesetzter name macht Logs also lesbar.
  • Der Handler prüft den Typ mit instanceof; die Meldung ist nur für Menschen.
  • error.cause ist der ganze ursprüngliche Fehler, mit eigenem name, message und stack.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 2

Ein HttpError mit Status

HttpError soll den HTTP-Status einer fehlgeschlagenen Anfrage tragen: new HttpError(503, "Service unavailable") hat den name HttpError, die Meldung und ein Feld status. isRetryable(error) ist nur für einen HttpError mit einem Status ab 500 true. Die Klasse im Starter setzt weder name noch status.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Was zeigt String(error) als name, und was ist error.status?

  2. Hinweis 2

    Setzen Sie nach super(message) this.name und this.status.

  3. Hinweis 3

    Reichen Sie auch options weiter, damit ein HttpError eine Ursache tragen kann.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export class HttpError extends Error {
  constructor(status, message, options) {
    super(message, options);
    this.name = "HttpError";
    this.status = status;
  }
}

export function isRetryable(error) {
  return error instanceof HttpError && error.status >= 500;
}
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

export class HttpError extends Error {
  constructor(status, message) {
    super(message);
  }
}

export function isRetryable(error) {
  return error instanceof HttpError && error.status >= 500;
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {HttpError, isRetryable} from './main.js';

test('ein HttpError hat name, Meldung und Status', () => {
  const error = new HttpError(503, 'Service unavailable');
  assert.equal(String(error), 'HttpError: Service unavailable', `String(error) ist ${String(error)}`);
  assert.equal(error.status, 503, `error.status ist ${error.status}`);
  assert.ok(error instanceof Error, 'ein HttpError sollte ein Error sein');
});

test('eine 503 lässt sich wiederholen, eine 404 nicht', () => {
  assert.equal(isRetryable(new HttpError(503, 'Service unavailable')), true, 'eine 503 sollte wiederholbar sein');
  assert.equal(isRetryable(new HttpError(404, 'Not found')), false, 'eine 404 sollte nicht wiederholbar sein');
});

test('andere Fehler werden nie wiederholt', () => {
  assert.equal(isRetryable(new TypeError('x is not a function')), false, 'ein TypeError sollte nicht wiederholbar sein');
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 2 von 2

Die Ursache behalten

loadConfig(text) parst eine als JSON geschriebene Konfiguration. Bei kaputtem JSON wirft es einen ConfigError mit der Meldung Config is not valid JSON und dem SyntaxError als Ursache. loadConfig übergibt {cause: error} bereits, aber die Ursache geht unterwegs verloren. Finden Sie die Stelle und beheben Sie es.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Wer liest das options-Objekt mit der Ursache darin?

  2. Hinweis 2

    Der constructor von ConfigError reicht das zweite Argument nie an Error weiter.

  3. Hinweis 3

    constructor(message, options) { super(message, options); … }

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export class ConfigError extends Error {
  constructor(message, options) {
    super(message, options);
    this.name = "ConfigError";
  }
}

export function loadConfig(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    throw new ConfigError("Config is not valid JSON", {cause: error});
  }
}
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

export class ConfigError extends Error {
  constructor(message) {
    super(message);
    this.name = "ConfigError";
  }
}

export function loadConfig(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    throw new ConfigError("Config is not valid JSON", {cause: error});
  }
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {ConfigError, loadConfig} from './main.js';

test('gültiges JSON kommt als Objekt zurück', () => {
  assert.deepEqual(loadConfig('{"port": 8080}'), {port: 8080}, 'loadConfig sollte gültiges JSON parsen');
});

test('kaputtes JSON wirft einen ConfigError', () => {
  assert.throws(() => loadConfig('{port'), ConfigError, 'loadConfig sollte einen ConfigError werfen');
});

test('der ConfigError behält den SyntaxError als Ursache', () => {
  try {
    loadConfig('{port');
    assert.fail('loadConfig hat nicht geworfen');
  } catch (error) {
    assert.ok(error.cause instanceof SyntaxError, `error.cause ist ${error.cause}`);
  }
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Häufige Fehler

this vor super() verwenden

class ValidationError extends Error {
  constructor(field, message) {
    this.field = field;
    super(message);
  }
}

new ValidationError("email", "Email is missing");

Was Node.js ausgibt

ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

Warum, und die Lösung

In einer Klasse, die eine andere erweitert, wird das Objekt vom constructor der Elternklasse erzeugt, this existiert also nicht, bevor super() gelaufen ist. Rufen Sie super(message, options) als erste Zeile des constructors auf und setzen Sie dann name und die anderen Felder.

Eine Unterklasse, die options fallen lässt

class LoadError extends Error {
  constructor(message) {
    super(message);
    this.name = "LoadError";
  }
}

try {
  throw new LoadError("Could not load", {cause: new Error("timeout")});
} catch (error) {
  console.log(error.cause.message);
}

Was Node.js ausgibt

TypeError: Cannot read properties of undefined (reading 'message')

Warum, und die Lösung

Error setzt cause nur aus dem options-Objekt, das es bekommt, und dieser constructor reicht es nie weiter, error.cause ist also undefined. Nehmen Sie options entgegen und geben Sie sie an super weiter: constructor(message, options) { super(message, options); … }.

Eine Fehlerklasse ohne new aufrufen

class NotFoundError extends Error {
  constructor(id) {
    super(`No user with id ${id}`);
    this.name = "NotFoundError";
  }
}

throw NotFoundError(2);

Was Node.js ausgibt

TypeError: Class constructor NotFoundError cannot be invoked without 'new'

Warum, und die Lösung

Das eingebaute Error() funktioniert auch ohne new, eine Klasse aber nie. Schreiben Sie throw new NotFoundError(2).

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Eine eigene Fehlerklasse

class ValidationError extends Error erzeugt einen neuen Fehlertyp. Rufen Sie in seinem constructor zuerst super(message, options) auf: Es setzt message und, falls options eines enthält, auch cause. Setzen Sie dann this.name = "ValidationError", sonst nennt sich der Fehler in Meldungen und Logs weiter Error, und ergänzen Sie Felder, die dem Handler helfen, etwa this.field = "email". Die Klasse ist auch ein Error: Sie hat einen stack, funktioniert mit throw, und instanceof Error ist true.

Nach Typ entscheiden, nicht nach Text

error instanceof NotFoundError fragt, um welche Art von Fehler es sich handelt; error.status oder error.field liefern die Einzelheiten. Code, der error.message mit einem Text vergleicht, bricht, sobald jemand die Formulierung verbessert. instanceof trifft auch Unterklassen; bei class DbError extends AppError prüfen Sie also die genauere Klasse zuerst. Promise.any wird mit einem AggregateError abgelehnt, dessen Array errors jeden einzelnen Fehler enthält.

Kontext hinzufügen, die Ursache behalten

Ein Fehler einer tieferen Ebene wie SyntaxError: Unexpected token o sagt wenig darüber, was das Programm gerade tat. Fangen Sie ihn und werfen Sie einen neuen Fehler, der es sagt, und übergeben Sie den ursprünglichen als cause: throw new Error("Could not read the settings", {cause: error}). Der Handler liest error.cause, und Node.js gibt beide Fehler mit ihren Stacks aus. Eine eigene Klasse muss options an super() weiterreichen, sonst geht die Ursache verloren.

Quellen

Zuletzt geprüft am 5. Oktober 2026