mitmario.dev

Das error-Ereignis

Node.js Sandbox 4 Min Lesezeit 3 BeispieleLektion 3 von 7

Ereignisnamen sind frei wählbar, und Node kümmert sich nicht darum, wie du sie nennst. Bis auf einen.

error ist der einzige Name mit einer Sonderbehandlung. Wird er ausgelöst und niemand hört zu, wirft Node den Fehler in den Prozess und beendet ihn.

Was das in der Praxis heißt

Ein error ohne Zuhörer
import { EventEmitter } from "node:events";

const melder = new EventEmitter();

console.log("vor emit");

melder.emit("error", new Error("etwas ist schiefgelaufen"));

console.log("nach emit");

Die Zeile nach dem emit wird nie erreicht. Im Terminal steht vor emit, und darunter in Rot der vollständige Stacktrace: die Codezeile, an der es passiert ist, dann der Satz mit der Fehlermeldung, dann die Aufrufkette, und ganz unten ein zweiter Block unter der Überschrift Emitted 'error' event at:.

Dieser zweite Block ist die eigentliche Auskunft, denn er sagt, wo ausgelöst wurde, und nicht nur, wo der Fehler gebaut wurde. Hier ist beides dieselbe Zeile 7, sie unterscheiden sich nur in der Spalte; in einem echten Programm liegen die zwei Stellen oft weit auseinander.

Drei Angaben darin sind Knöpfe, weil sie auf eine Datei zeigen, die du hier vor dir hast. Ein Klick darauf springt in den Editor auf die Zeile. Was aus node:internal kommt, bleibt Text: Dort kannst du nichts ändern, und der Kurs schickt dich auch nicht hin.

Der Exit-Code ist 1, und den zeigt keine Fläche. Wenn du ihn sehen willst, starte die Sandbox und tipp im Terminal node ohne-zuhoerer.js; echo $?. Derselbe Lauf, und in der letzten Zeile steht die 1. Das Semikolon gehört dazu: echo $? gibt den Code des Befehls davor aus, und als eigener Befehl abgeschickt liefe es in einer frischen Shell und meldete die Null von sich selbst.

Zum Vergleich derselbe Ablauf mit einem einzigen zusätzlichen Zuhörer.

Derselbe Fehler mit Zuhörer
import { EventEmitter } from "node:events";

const melder = new EventEmitter();

melder.on("error", (fehler) => console.log(`abgefangen: ${fehler.message}`));

console.log("vor emit");

melder.emit("error", new Error("etwas ist schiefgelaufen"));

console.log("nach emit");

Ein on("error", ...), und aus dem Abbruch wird ein ganz normaler Programmablauf. Der Prozess läuft weiter, nach emit kommt dran, der Exit-Code ist 0.

Und zum Beweis, dass wirklich der Name die Sonderregel trägt und nicht das Error-Objekt darin:

Jeder andere Name bleibt folgenlos
import { EventEmitter } from "node:events";

const melder = new EventEmitter();

melder.emit("warnung", new Error("kein Zuhoerer, keine Sonderregel"));

console.log("laeuft einfach weiter");

Genau derselbe Fehler, nur unter einem anderen Ereignisnamen, und es passiert schlicht nichts. Auch kein Hinweis. Ein Ereignis ohne Zuhörer ist normalerweise völlig in Ordnung.

Warum Node so hart reagiert

Auf den ersten Blick wirkt das übertrieben. Ein Programm wegen eines nicht abgeholten Ereignisses zu beenden, klingt nach Schikane.

Die Alternative ist aber schlechter. Ein error, den niemand abholt, bedeutet: Etwas ist schiefgegangen, und keine Stelle im Programm weiß davon. Ohne die Sonderregel würde dein Programm stillschweigend weiterlaufen, in einem Zustand, mit dem niemand gerechnet hat. Ein Datenbankschreiber ohne Verbindung, ein Stream ohne Datei, ein Server ohne Port.

Ein lauter Abbruch ist besser als ein stiller Fehlzustand. Das ist dieselbe Haltung wie beim Callback-Stil aus Lektion 6.3, nur andersherum: Dort war das Problem, dass ein ignorierter Fehler still bleibt. Hier hat Node sich entschieden, das nicht zuzulassen.

Wo dir das begegnet

Du musst dafür keinen eigenen Emitter gebaut haben. Alles, was in Node melden kann, meldet auch Fehler auf diesem Weg:

Ein Stream meldet error, wenn die Datei verschwindet oder die Platte voll ist. Ein Server meldet error, wenn der Port belegt ist. Ein Socket meldet error, wenn die Verbindung abreißt. Ein Kindprozess meldet error, wenn sich das Programm gar nicht erst starten ließ.

Daraus folgt eine Regel, die kurz ist und viel wert: An jeden Emitter, der Fehler melden kann, gehört ein on("error", ...), und zwar bevor er losläuft. Nachträglich anmelden hilft nicht, denn zu dem Zeitpunkt ist der Prozess schon weg.

Ein try/catch hilft dir dabei meistens nicht

Das ist der Punkt, an dem die Lektion praktisch wird. emit ruft seine Zuhörer synchron auf, und deshalb kannst du ein emit in deinem eigenen Code tatsächlich mit try und catch umschließen.

Nur ist das in echtem Code fast nie die Stelle, an der es passiert. Ein Stream meldet seinen Fehler nicht, während du createReadStream aufrufst, sondern Sekunden später aus der Ereignisschleife heraus. Dein try ist da längst durchgelaufen, und catch sieht nichts.

Für Fehler aus einem Emitter gibt es genau ein Mittel, und das heißt on("error", ...).

In Lektion 16.4 taucht derselbe Mechanismus noch einmal auf, dann für Promises, die niemand abfängt. Auch dort gilt: Node beendet lieber, als weiterzulaufen und so zu tun, als sei nichts.

Zum Mitnehmen

error ist der einzige Ereignisname mit einer Sonderregel: Hört niemand zu, beendet Node den Prozess.

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.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.