mitmario.dev

Eigene Fehler werfen

JavaScript Im Browser 3 Min Lesezeit 3 BeispieleLektion 3 von 6

Bis jetzt hast du Fehler nur eingesammelt. Jetzt drehen wir es um: Deine eigenen Funktionen dürfen auch welche verursachen.

Das klingt erst einmal nach dem Gegenteil von guter Arbeit. Es ist aber eine der wirksamsten Sachen, die du für dich selbst tun kannst. Eine Funktion, die bei falscher Benutzung deutlich wird, spart dir Stunden Suche in Code, der viel weiter unten steht.

throw und was danach passiert

throw beendet die Funktion sofort
function alterPruefen(jahre) {
  console.log("Prüfe", jahre);
  if (jahre < 0) {
    throw new Error("Ein Alter kann nicht negativ sein");
  }
  console.log("Diese Zeile kommt nur bei gültigem Alter dran.");
  return jahre;
}

try {
  alterPruefen(34);
  alterPruefen(-5);
  console.log("Hierhin kommen wir nicht.");
} catch (fehler) {
  console.error(fehler);
}

throw new Error("Text") tut zwei Dinge. Es baut ein Error-Objekt mit deiner Meldung, und es beendet die Funktion an dieser Stelle. Nichts, was danach in der Funktion steht, läuft noch.

Dann geht der Fehler nach oben: zum Aufrufer, zu dessen Aufrufer, und so weiter, bis jemand ein catch darum hat. Findet sich niemand, landet er als ungefangener Fehler in der Console, und das Programm steht.

Im ersten Beispiel siehst du beides. Der erste Aufruf läuft durch, der zweite bricht mitten in der Funktion ab, und der console.log direkt danach im try-Block kommt ebenfalls nicht mehr dran.

Werfen oder zurückgeben

Werfen oder zurückgeben
const lager = [
  { id: "a1", name: "Tastatur" },
  { id: "b2", name: "Maus" },
];

// Nichts gefunden ist ein erwartbarer Fall. Kein Wurf.
function suchen(id) {
  return lager.find((artikel) => artikel.id === id) ?? null;
}

// Eine fehlende id ist ein Fehler im aufrufenden Code. Wurf.
function suchenStreng(id) {
  if (typeof id !== "string" || id === "") {
    throw new Error(`suchenStreng erwartet eine id als Text, bekommen: ${typeof id}`);
  }
  return lager.find((artikel) => artikel.id === id) ?? null;
}

console.log("Vorhanden:", suchen("a1"));
console.log("Nicht vorhanden:", suchen("zzz"));

try {
  suchenStreng(undefined);
} catch (fehler) {
  console.error(fehler);
}

Das ist die Frage, um die es in dieser Lektion eigentlich geht, und sie ist keine Geschmackssache.

Wirf, wenn der Aufrufer etwas falsch gemacht hat. Wenn deine Funktion eine Zahl braucht und einen Text bekommt, kann sie nicht sinnvoll weiterarbeiten. Ein Rückgabewert wie null würde den Fehler weiterreichen, und er taucht drei Funktionen später als TypeError auf, wo niemand mehr weiß, wo er herkommt.

Gib einen Wert zurück, wenn der Fall vorgesehen ist. „Nichts gefunden” ist kein Fehler. Es ist ein völlig normales Ergebnis einer Suche, und der Aufrufer hat nichts falsch gemacht. Dafür ist null genau richtig.

Im zweiten Beispiel stehen beide nebeneinander. suchen("zzz") liefert brav null, denn dieser Artikel existiert eben nicht. suchenStreng(undefined) wirft, denn undefined ist keine id, und wer das übergibt, hat einen Fehler im Code.

Ein Merksatz, der fast immer trägt: Ein Fehler ist etwas, das nicht passieren sollte. Alles, was passieren darf, ist ein Rückgabewert.

Immer ein Error, nie ein Text

Ein Error trägt mehr als ein Text
function mitText() {
  throw "So bitte nicht";
}

function mitError() {
  throw new Error("So ist es richtig");
}

try {
  mitText();
} catch (fehler) {
  console.log("Gefangen:", fehler);
  console.log("typeof:", typeof fehler);
  console.log("stack:", fehler.stack);
}

try {
  mitError();
} catch (fehler) {
  console.log("Gefangen:", fehler.message);
  console.log("typeof:", typeof fehler);
  console.log("stack vorhanden:", typeof fehler.stack === "string");
}

Technisch darfst du alles werfen. throw "kaputt" ist gültiges JavaScript, genauso wie throw 42.

Tu es trotzdem nicht. Im dritten Beispiel siehst du warum: Der geworfene Text kommt als Text an, und er hat weder name noch stack. Damit ist die wichtigste Information weg, nämlich wo das passiert ist.

Dazu kommt ein praktischer Grund. Fremder Code, der deinen aufruft, geht davon aus, dass er ein Error fängt. Er liest fehler.message, und bei einem geworfenen Text steht dort undefined. Du bringst also nicht nur dich selbst in Schwierigkeiten.

Die Regel ist deshalb kurz: Wirf immer new Error(...).

Was in die Meldung gehört

Eine gute Fehlermeldung beantwortet zwei Fragen: Was wurde erwartet, und was kam an.

"Ungültige Eingabe" beantwortet keine davon. "teile erwartet zwei Zahlen, bekommen: string und number" beantwortet beide, und du weißt beim Lesen sofort, wonach du suchst.

Wenn es hilft, nimm den Namen der Funktion mit hinein. Im Stacktrace steht er zwar ohnehin, aber Meldungen wandern manchmal in Protokolle, in denen der Stacktrace fehlt.

Was nicht in die Meldung gehört, sind Dinge, die niemanden angehen: Passwörter, Schlüssel, ganze Datensätze. Eine Fehlermeldung landet schneller an einer öffentlichen Stelle, als einem lieb ist.

Eigene Fehlerarten

Für den Anfang reicht Error völlig aus. Der Vollständigkeit halber: Man kann eigene Arten bauen, die sich wie TypeError und Kollegen benutzen lassen.

Das braucht Klassen, und die kommen in Abschnitt 18. Der Nutzen davon ist, dass ein catch unterscheiden kann, um welche Sorte Problem es sich handelt, ohne im Meldungstext nach Stichwörtern zu suchen. Bis dahin: eine deutliche Meldung tut es auch.

Zum Mitnehmen

Wirf, wenn der Aufrufer etwas falsch gemacht hat. Gib einen Wert zurück, wenn der Fall vorgesehen ist. Ein leeres Suchergebnis ist kein Fehler.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.