Streams in Node.js: Richtig Schluss machen
Streams sind eines der wichtigsten Konzepte in Node.js, denn sie bilden die Grundlage für die effiziente Verarbeitung großer Datenmengen. Zwar ist die Verwendung zunächst wenig intuitiv, doch ergibt sich das irgendwann. Schwieriger sieht es jedoch beim Aufräumen aus.
- Golo Roden
Streams sind eines der wichtigsten Konzepte in Node.js, denn sie bilden die Grundlage für die effiziente Verarbeitung großer Datenmengen. Zwar ist die Verwendung zunächst wenig intuitiv, doch ergibt sich das irgendwann. Schwieriger sieht es jedoch beim Aufräumen aus.
Der in der JavaScript-Szene bekannte Entwickler Dominic Tarr hat Streams einmal als die beste und zugleich am häufigsten missverstandene Idee von Node.js bezeichnet. Das verwundert, denn das Konzept ist nicht sonderlich neu: Beispielsweise ist das Verwenden von Streams in Unix an der Tagesordnung, um Daten auf der Kommandozeile zu verarbeiten.
Ăśber kurz oder lang findet man in der Regel jedoch den Zugang zu Streams und findet heraus, wie die Funktionen write und end bei schreibbaren Streams funktionieren, beziehungsweise was die Ereignisse data, readable und end bei lesbaren Streams bedeuten. Im Zweifelsfall hilft beispielsweise das Video "How streams help to raise Node.js performance" weiter.
Streams und Observer
Wichtig ist, sich bewusst zu machen, dass lesbare Streams das Entwurfsmuster Observer implementieren: Mithilfe der Ereignisse data und readable kann man Funktionen registrieren, die im Falle des Eintreffens von Daten ausgelöst werden.
Das bedeutet allerdings auch, dass es ratsam ist, die ereignisbehandelnden Funktionen von den Ereignissen wieder abzuhängen, wenn man sie nicht länger benötigt. Andernfalls riskiert man, dass Streams im Speicher hängen bleiben und die von ihnen belegten Ressourcen nicht von der Garbage Collection freigegeben werden können.
Dazu kennen Streams die Funktion removeListener, die sie von der den Streams zugrunde liegenden Klasse EventEmitter erben. Es gilt sicherzustellen, dass alle angehängten Funktionen auf dem Weg wieder abgehängt werden.
Patterns fĂĽr die Praxis
In der Praxis hat es sich bewährt, das Abhängen an der gleichen Stelle vorzunehmen wie das vorige Anhängen. Das ist enorm hilfreich, denn auf die Art bleibt stets überschaubar, welche Ereignisse eines Streams sauber aufgeräumt werden und bei welchen noch Handlungsbedarf besteht.
Außerdem lässt sich das grundlegende Verwenden eines Streams in folgendem Codeschnipsel zusammenfassen, das als Kopiervorlage gute Dienste leisten kann:
const myStream = getStreamFrom(...);
let unsubscribe;
const onData = function (data) {
// Handle data
};
const onEnd = function () {
unsubscribe();
// Handle end of stream
};
const onError = function (err) {
unsubscribe();
// Handle error
};
unsubscribe = function () {
myStream.removeListener('data', onData);
myStream.removeListener('end', onEnd);
myStream.removeListener('error', onError);
};
myStream.on('data', onData);
myStream.on('end', onEnd);
myStream.on('error', onError);
Den Ăśberblick behalten
Auf einen Blick sind die drei Funktionen onData, onEnd und onError zu erkennen, und sie sind gemeinsam gruppiert. Das gleiche gilt für das An- beziehungsweise das Abhängen der Funktionen.
Auch wenn das gezeigte Vorgehen nicht stets für alle Fälle passt, ist es doch ein guter Ausgangspunkt, wie man den Umgang mit Streams strukturieren kann. Fügt man den Code dem persönlichen Repertoire als vorbereitetes Schnipsel hinzu, lässt sich das Verwenden von Streams standardisieren, was zu guter Letzt zu einer Verbesserung der Verständlichkeit des Codes führt.
tl;dr: Damit Node.js aber nicht mehr benötigte Streams aus dem Speicher entfernen kann, ist es ratsam, die ereignisbehandelnden Funktionen abzuhängen. Dabei leistet ein Codeschnipsel gute Hilfe, das den Vorgang standardisiert. ()