Alles, was Sie brauchen, um Streams in Node.js zu verstehen

Blog

Alles, was Sie brauchen, um Streams in Node.js zu verstehen

Alles, was Sie brauchen, um Streams in Node.js zu verstehen

Node.js-Streams haben den Ruf, schwer zu bearbeiten und noch schwerer zu verstehen zu sein. Nun, ich habe gute Nachrichten für Sie – das ist nicht mehr der Fall.

Im Laufe der Jahre haben Entwickler viele Pakete mit dem einzigen Ziel erstellt, die Arbeit mit Streams zu erleichtern. Aber in diesem Artikel konzentriere ich mich auf die Einheimischen Node.js-Stream-API .

Streams sind die beste und am meisten missverstandene Idee von Node.

— Dominic Tarra

Was genau sind Streams?

Streams sind Sammlungen von Daten – genau wie Arrays oder Strings. Der Unterschied besteht darin, dass Streams möglicherweise nicht alle gleichzeitig verfügbar sind und nicht in den Speicher passen müssen. Dies macht Streams wirklich leistungsstark, wenn Sie mit großen Datenmengen arbeiten oder Daten, die von einer externen Quelle stammen Brocken auf einmal.

Bei Streams geht es jedoch nicht nur darum, mit Big Data zu arbeiten. Sie geben uns auch die Macht der Zusammensetzbarkeit in unserem Code. Genauso wie wir leistungsstarke Linux-Befehle erstellen können, indem wir andere kleinere Linux-Befehle weiterleiten, können wir in Node genau dasselbe mit Streams tun.

const grep = ... // A stream for the grep output const wc = ... // A stream for the wc input grep.pipe(wc)

Viele der integrierten Module in Node implementieren die Streaming-Schnittstelle:

Die obige Liste enthält einige Beispiele für native Node.js-Objekte, die auch lesbare und beschreibbare Streams sind. Einige dieser Objekte sind sowohl lesbare als auch beschreibbare Streams, wie TCP-Sockets, zlib und Krypto-Streams.

Beachten Sie, dass die Objekte auch eng miteinander verbunden sind. Während eine HTTP-Antwort auf dem Client ein lesbarer Stream ist, ist sie auf dem Server ein beschreibbarer Stream. Dies liegt daran, dass wir im HTTP-Fall grundsätzlich von einem Objekt lesen (|_+_|) und in das andere schreiben (|_+_|).

Beachten Sie auch, wie die |_+_| Streams (|_+_|, |_+_|, |_+_|) haben die inversen Stream-Typen, wenn es um untergeordnete Prozesse geht. Dies ermöglicht eine wirklich einfache Möglichkeit, diese Streams vom Hauptprozess aus zu und von diesen zu leiten |_+_| Ströme.

Ein Streams-Praxisbeispiel

Theorie ist toll, aber oft nicht 100% überzeugend. Sehen wir uns ein Beispiel an, das den Unterschied zeigt, den Streams im Code in Bezug auf den Speicherverbrauch machen können.

Lassen Sie uns zuerst eine große Datei erstellen:

Tic-Tac-Toe-Javascript-Tutorial
http.IncomingMessage

Sehen Sie, was ich verwendet habe, um diese große Datei zu erstellen. Ein beschreibbarer Stream!

Die |_+_| -Modul kann verwendet werden, um Dateien über eine Stream-Schnittstelle zu lesen und in Dateien zu schreiben. Im obigen Beispiel schreiben wir das |_+_| durch einen beschreibbaren Strom 1 Million Zeilen mit einer Schleife.

Wenn Sie das obige Skript ausführen, wird eine Datei mit einer Größe von etwa 400 MB generiert.

Hier ist ein einfacher Node-Webserver, der ausschließlich für |_+_| entwickelt wurde:

http.ServerResponse

Wenn der Server eine Anfrage erhält, stellt er die große Datei mit der asynchronen Methode |_+_| bereit. Aber hey, es ist nicht so, dass wir die Ereignisschleife blockieren oder so. Alles ist großartig, oder? Rechts?

Sehen wir uns an, was passiert, wenn wir den Server ausführen, eine Verbindung zu ihm herstellen und dabei den Speicher überwachen.

Als ich den Server lief, startete er mit einer normalen Speichermenge von 8,7 MB:

Dann habe ich mich mit dem Server verbunden. Beachten Sie, was mit dem verbrauchten Speicher passiert ist:

Wow – der Speicherverbrauch sprang auf 434,8 MB.

Wir setzen im Grunde das ganze |_+_| Inhalt im Speicher, bevor wir ihn in das Antwortobjekt geschrieben haben. Dies ist sehr ineffizient.

Das HTTP-Antwortobjekt (|_+_| im obigen Code) ist ebenfalls ein beschreibbarer Stream. Das heißt, wenn wir einen lesbaren Stream haben, der den Inhalt von |_+_| darstellt, können wir diese beiden einfach aufeinander leiten und erreichen fast das gleiche Ergebnis, ohne ~400 MB Speicher zu verbrauchen.

Knoten |_+_| Modul kann uns einen lesbaren Stream für jede Datei mit dem |_+_| . geben Methode. Wir können das an das Antwortobjekt weiterleiten:

stdio

Wenn Sie sich nun mit diesem Server verbinden, passiert etwas Magisches (sehen Sie sich den Speicherverbrauch an):

Was ist los?

Wenn ein Client nach dieser großen Datei fragt, streamen wir sie Stück für Stück, was bedeutet, dass wir sie überhaupt nicht im Speicher zwischenspeichern. Der Speicherverbrauch wuchs um etwa 25 MB und das wars.

Sie können dieses Beispiel an seine Grenzen bringen. Regenerieren Sie die |_+_| mit fünf Millionen Zeilen statt nur einer Million, was die Datei auf weit über 2 GB bringen würde, und das ist tatsächlich größer als das Standardpufferlimit in Node.

Pandas ersetzen durch nan

Wenn Sie versuchen, diese Datei mit |_+_| bereitzustellen, ist dies standardmäßig nicht möglich (Sie können die Grenzen ändern). Aber mit |_+_| gibt es überhaupt kein Problem damit, 2 GB Daten an den Requester zu streamen, und das Beste daran ist, dass die Prozessspeichernutzung ungefähr gleich bleibt.

Streams 101

Es gibt vier grundlegende Streamtypen in Node.js: Readable, Writeable, Duplex und Transform Streams.

  • Ein lesbarer Stream ist eine Abstraktion für eine Quelle, aus der Daten konsumiert werden können. Ein Beispiel dafür ist das |_+_| Methode.
  • Ein beschreibbarer Stream ist eine Abstraktion für ein Ziel, in das Daten geschrieben werden können. Ein Beispiel dafür ist das |_+_| Methode.
  • Ein Duplex-Stream ist sowohl lesbar als auch beschreibbar. Ein Beispiel dafür ist ein TCP-Socket.
  • Ein Transformationsstream ist im Grunde ein Duplexstream, der verwendet werden kann, um die Daten beim Schreiben und Lesen zu ändern oder umzuwandeln. Ein Beispiel dafür ist das |_+_| stream, um die Daten mit gzip zu komprimieren. Sie können sich einen Transformationsstream als Funktion vorstellen, bei der die Eingabe der beschreibbare Streamteil und die Ausgabe der lesbare Streamteil ist. Möglicherweise hören Sie auch Transformationsstreams, die als . bezeichnet werden durch Bäche .

Alle Streams sind Instanzen von |_+_|. Sie geben Ereignisse aus, mit denen Daten gelesen und geschrieben werden können. Wir können Stream-Daten jedoch auf einfachere Weise konsumieren, indem wir |_+_| . verwenden Methode.

Die Rohrmethode

Hier ist die magische Linie, die Sie sich merken müssen:

stdin

In dieser einfachen Zeile leiten wir die Ausgabe eines lesbaren Streams – die Datenquelle – als Eingabe eines beschreibbaren Streams – das Ziel. Die Quelle muss ein lesbarer Stream und das Ziel muss ein beschreibbarer sein. Natürlich können sie auch Duplex-/Transform-Streams sein. Tatsächlich können wir, wenn wir in einen Duplex-Stream einleiten, Pipe-Aufrufe verketten, genau wie wir es in Linux tun:

stdout

Die |_+_| -Methode gibt den Zielstream zurück, wodurch wir die obige Verkettung durchführen konnten. Für Streams |_+_| (lesbar), |_+_| und |_+_| (Duplex) und |_+_| (beschreibbar), können wir:

stderr

Die |_+_| -Methode ist der einfachste Weg, um Streams zu konsumieren. Es wird im Allgemeinen empfohlen, entweder die |_+_| -Methode verwenden oder Streams mit Ereignissen konsumieren, aber vermeiden Sie es, diese beiden zu vermischen. Normalerweise, wenn Sie |_+_| . verwenden -Methode müssen Sie keine Ereignisse verwenden, aber wenn Sie die Streams auf benutzerdefiniertere Weise konsumieren müssen, sind Ereignisse der richtige Weg.

Ereignisse streamen

Neben dem Lesen von einer lesbaren Stream-Quelle und dem Schreiben in ein beschreibbares Ziel kann die |_+_| -Methode verwaltet automatisch einige Dinge auf dem Weg. Es behandelt beispielsweise Fehler, Dateiende und Fälle, in denen ein Stream langsamer oder schneller ist als der andere.

Streams können jedoch auch direkt mit Ereignissen konsumiert werden. Hier ist der vereinfachte ereignisäquivalente Code dessen, was die |_+_| Methode hauptsächlich zum Lesen und Schreiben von Daten:

stdio

Hier ist eine Liste der wichtigen Ereignisse und Funktionen, die mit lesbaren und beschreibbaren Streams verwendet werden können:

Die Ereignisse und Funktionen hängen irgendwie zusammen, da sie normalerweise zusammen verwendet werden.

Die wichtigsten Ereignisse in einem lesbaren Stream sind:

  • Die |_+_| -Ereignis, das immer dann ausgegeben wird, wenn der Stream einen Datenblock an den Verbraucher weitergibt
  • Die |_+_| -Ereignis, das ausgegeben wird, wenn keine Daten mehr aus dem Stream zu konsumieren sind.

Die wichtigsten Ereignisse in einem beschreibbaren Stream sind:

  • Die |_+_| Ereignis, das ein Signal ist, dass der beschreibbare Stream mehr Daten empfangen kann.
  • Die |_+_| -Ereignis, das ausgegeben wird, wenn alle Daten in das zugrunde liegende System geleert wurden.

Ereignisse und Funktionen können kombiniert werden, um eine individuelle und optimierte Nutzung von Streams zu ermöglichen. Um einen lesbaren Stream zu konsumieren, können wir |_+_|/|_+_| . verwenden Methoden oder die |_+_|/|_+_|/|_+_| Methoden. Um einen beschreibbaren Stream zu konsumieren, können wir ihn zum Ziel von |_+_|/|_+_| machen oder einfach mit dem |_+_| . darauf schreiben -Methode und rufen Sie die |_+_| Methode, wenn wir fertig sind.

Angehaltene und fließende Modi von lesbaren Streams

Lesbare Streams haben zwei Hauptmodi, die sich darauf auswirken, wie wir sie konsumieren:

  • Sie können entweder in der pausiert Modus
  • Oder im fließend Modus

Diese Modi werden manchmal als Pull- und Push-Modi bezeichnet.

Alle lesbaren Streams starten standardmäßig im angehaltenen Modus, können jedoch bei Bedarf problemlos in den fließenden Modus und wieder zurück in den angehaltenen Modus geschaltet werden. Manchmal geschieht die Umschaltung automatisch.

Wenn sich ein lesbarer Stream im angehaltenen Modus befindet, können wir |_+_| . verwenden Methode, um bei Bedarf aus dem Stream zu lesen, jedoch für einen lesbaren Stream im Fließmodus, die Daten fließen kontinuierlich und wir müssen auf Ereignisse hören, um sie zu konsumieren.

was bedeutet Bereitstellungsdienst

Im Flow-Modus können Daten tatsächlich verloren gehen, wenn keine Verbraucher verfügbar sind, um sie zu verarbeiten. Aus diesem Grund benötigen wir bei einem lesbaren Stream im Fließmodus ein |_+_| Ereignishandler. Tatsächlich fügt man einfach ein |_+_| . hinzu Event-Handler schaltet einen angehaltenen Stream in den Flow-Modus und entfernt das |_+_| Event-Handler schaltet den Stream zurück in den angehaltenen Modus. Einiges davon erfolgt aus Gründen der Abwärtskompatibilität mit der älteren Node-Streams-Schnittstelle.

Um manuell zwischen diesen beiden Stream-Modi zu wechseln, können Sie die |_+_| und |_+_| Methoden.

Beim Konsumieren von lesbaren Streams mit dem |_+_| Methode müssen wir uns um diese Modi keine Sorgen machen, da |_+_| verwaltet sie automatisch.

Implementieren von Streams

Wenn wir über Streams in Node.js sprechen, gibt es zwei unterschiedliche Hauptaufgaben:

  • Die Aufgabe von implementieren die Ströme.
  • Die Aufgabe von konsumierend Sie.

Bisher haben wir nur davon gesprochen, Streams zu konsumieren. Lassen Sie uns einige umsetzen!

Stream-Implementierer sind normalerweise diejenigen, die |_+_| die |_+_| Modul.

Implementieren eines beschreibbaren Streams

Um einen beschreibbaren Stream zu implementieren, müssen wir |_+_| . verwenden Konstruktor aus dem Stream-Modul.

const fs = require('fs'); const file = fs.createWriteStream('./big.file'); for(let i=0; i<= 1e6; i++) { file.write('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. '); } file.end();

Wir können einen beschreibbaren Stream auf viele Arten implementieren. Wir können zum Beispiel die |_+_| Konstruktor, wenn wir wollen

fs

Ich bevorzuge jedoch den einfacheren Konstruktor-Ansatz. Wir erstellen einfach ein Objekt aus dem |_+_| -Konstruktor und übergeben Sie ihm eine Reihe von Optionen. Die einzige erforderliche Option ist ein |_+_| Funktion, die den zu schreibenden Datenblock verfügbar macht.

big.file

Diese Schreibmethode benötigt drei Argumente.

  • Die Brocken ist normalerweise ein Puffer, es sei denn, wir konfigurieren den Stream anders.
  • Die Codierung In diesem Fall ist ein Argument erforderlich, aber normalerweise können wir es ignorieren.
  • Die zurückrufen ist eine Funktion, die wir aufrufen müssen, nachdem wir mit der Verarbeitung des Datenblocks fertig sind. Es signalisiert, ob der Schreibvorgang erfolgreich war oder nicht. Um einen Fehler zu signalisieren, rufen Sie den Callback mit einem Fehlerobjekt auf.

In |_+_|, wir einfach |_+_| den Chunk als String und rufen Sie die |_+_| danach ohne Fehler, um den Erfolg anzuzeigen. Dies ist eine sehr einfache und wahrscheinlich nicht so nützliche rauswerfen Strom. Es wird alles zurückgeben, was es empfängt.

Um diesen Stream zu konsumieren, können wir ihn einfach mit |_+_| verwenden, was ein lesbarer Stream ist, also können wir einfach |_+_| . weiterleiten in unser |_+_|.

Wenn wir den obigen Code ausführen, geben wir alles, was wir in |_+_| . eingeben wird mit dem |_+_| . zurückgesendet |_+_| Leitung.

Dies ist kein sehr nützlicher Stream für die Implementierung, da er tatsächlich bereits implementiert und integriert ist. Dies entspricht weitgehend |_+_|. Wir können einfach pfeifen |_+_| in |_+_| und wir erhalten mit dieser einzelnen Zeile genau die gleiche Echofunktion:

big.file

Implementieren Sie einen lesbaren Stream

Um einen lesbaren Stream zu implementieren, benötigen wir die |_+_| Schnittstelle und konstruiere daraus ein Objekt und implementiere ein |_+_| -Methode im Konfigurationsparameter des Streams:

const fs = require('fs'); const server = require('http').createServer(); server.on('request', (req, res) => { fs.readFile('./big.file', (err, data) => { if (err) throw err; res.end(data); }); }); server.listen(8000);

Es gibt eine einfache Möglichkeit, lesbare Streams zu implementieren. Wir können einfach direkt |_+_| die Daten, die die Verbraucher konsumieren sollen.

fs.readFile

Wenn wir |_+_| ein |_+_| Objekt, das heißt, wir wollen signalisieren, dass der Stream keine Daten mehr enthält.

Um diesen einfachen lesbaren Stream zu konsumieren, können wir ihn einfach in den beschreibbaren Stream |_+_| umleiten.

Wenn wir den obigen Code ausführen, lesen wir alle Daten von |_+_| und Echo es zum Standard heraus. Sehr einfach, aber auch nicht sehr effizient.

Wir pushen im Grunde alle Daten im Stream Vor an |_+_| weiterleiten. Der viel bessere Weg ist, Daten zu pushen auf Nachfrage , wenn ein Verbraucher danach fragt. Wir können das tun, indem wir |_+_| . implementieren Methode im Konfigurationsobjekt:

big.file

Wenn die Methode read für einen lesbaren Stream aufgerufen wird, kann die Implementierung Teildaten in die Warteschlange verschieben. Zum Beispiel können wir einen Buchstaben nach dem anderen drücken, beginnend mit dem Zeichencode 65 (der A darstellt) und diesen bei jedem Drücken inkrementieren:

res

Während der Verbraucher einen lesbaren Stream liest, wird das |_+_| -Methode wird weiterhin ausgelöst, und wir werden weitere Buchstaben senden. Wir müssen diesen Zyklus irgendwo anhalten, und deshalb eine if-Anweisung, um null zu pushen, wenn der currentCharCode größer als 90 ist (was Z darstellt).

Dieser Code entspricht dem einfacheren, mit dem wir angefangen haben, aber jetzt pushen wir Daten auf Abruf, wenn der Verbraucher danach fragt. Das solltest du immer tun.

Implementieren von Duplex-/Transform-Streams

Mit Duplex-Streams können wir sowohl lesbare als auch beschreibbare Streams mit demselben Objekt implementieren. Es ist, als ob wir von beiden Schnittstellen erben.

Golang-String zum Schweben

Hier ist ein Beispiel-Duplex-Stream, der die beiden oben implementierten beschreibbaren und lesbaren Beispiele kombiniert:

big.file

Durch Kombination der Methoden können wir diesen Duplex-Stream verwenden, um die Buchstaben von A bis Z zu lesen, und wir können ihn auch für seine Echofunktion verwenden. Wir leiten das lesbare |_+_| streamen in diesen Duplex-Stream, um die Echo-Funktion zu verwenden, und wir leiten den Duplex-Stream selbst in das beschreibbare |_+_| streamen, um die Buchstaben A bis Z zu sehen.

Es ist wichtig zu verstehen, dass die lesbaren und beschreibbaren Seiten eines Duplex-Streams völlig unabhängig voneinander funktionieren. Dies ist lediglich eine Gruppierung von zwei Features zu einem Objekt.

Ein Transformationsstrom ist der interessantere Duplexstrom, da seine Ausgabe aus seiner Eingabe berechnet wird.

Für einen Transformationsstream müssen wir |_+_| . nicht implementieren oder |_+_| -Methoden müssen wir nur ein |_+_| . implementieren Methode, die beides kombiniert. Es hat die Unterschrift des |_+_| -Methode und wir können sie verwenden, um |_+_| auch Daten.

Hier ist ein einfacher Transformationsstream, der alles zurückgibt, was Sie eingeben, nachdem Sie es in das Großbuchstabenformat umgewandelt haben:

fs

In diesem Transformationsstream, den wir genau wie im vorherigen Duplexstream-Beispiel verwenden, haben wir nur ein |_+_| . implementiert Methode. Bei dieser Methode konvertieren wir |_+_| in seine Großbuchstaben-Version und dann |_+_| diese Version als lesbarer Teil.

Streams-Objektmodus

Standardmäßig erwarten Streams Buffer/String-Werte. Es gibt ein |_+_| Flag, das wir so setzen können, dass der Stream jedes JavaScript-Objekt akzeptiert.

Hier ist ein einfaches Beispiel, um das zu demonstrieren. Die folgende Kombination von Transformationsstreams ermöglicht es einem Feature, eine Zeichenfolge von durch Kommas getrennten Werten einem JavaScript-Objekt zuzuordnen. Also |_+_| wird zu |_+_|.

createReadStream

Wir übergeben die Eingabezeichenfolge (zum Beispiel |_+_|) durch |_+_| die ein Array als seine lesbaren Daten (|_+_|) pusht. Hinzufügen von |_+_| Flag in diesem Stream ist notwendig, weil wir ein Objekt dorthin verschieben, keine Zeichenfolge.

Wir nehmen dann das Array und leiten es in die |_+_| Strom. Wir brauchen ein |_+_| Flag, damit dieser Stream ein Objekt akzeptiert. Es wird auch ein Objekt (das in ein Objekt gemappte Eingabearray) pushen und deshalb brauchten wir auch die |_+_| auch dort Flagge. Die letzten |_+_| stream akzeptiert ein Objekt, schiebt aber einen String aus, und deshalb brauchten wir nur ein |_+_| Flagge dort. Der lesbare Teil ist ein normaler String (das stringifizierte Objekt).

Die integrierten Transformationsstreams von Node

Node verfügt über einige sehr nützliche integrierte Transformationsstreams. Nämlich die zlib- und Crypto-Streams.

Hier ist ein Beispiel, das |_+_| . verwendet Stream kombiniert mit dem |_+_| lesbare/beschreibbare Streams zum Erstellen eines Dateikomprimierungsskripts:

const fs = require('fs'); const server = require('http').createServer(); server.on('request', (req, res) => { const src = fs.createReadStream('./big.file'); src.pipe(res); }); server.listen(8000);

Sie können dieses Skript verwenden, um jede Datei, die Sie als Argument übergeben, als gzip zu speichern. Wir leiten einen lesbaren Stream für diese Datei in den integrierten zlib-Transformationsstream und dann in einen beschreibbaren Stream für die neue gzip-Datei. Einfach.

Das Coole an der Verwendung von Pipes ist, dass wir sie bei Bedarf mit Events kombinieren können. Sagen wir zum Beispiel, ich möchte, dass der Benutzer eine Fortschrittsanzeige sieht, während das Skript arbeitet, und eine Fertig-Meldung, wenn das Skript fertig ist. Seit dem |_+_| -Methode den Zielstream zurückgibt, können wir auch die Registrierung von Ereignishandlern verketten:

big.file

Also mit dem |_+_| Methode können wir Streams problemlos konsumieren, aber wir können unsere Interaktion mit diesen Streams bei Bedarf mithilfe von Ereignissen weiter anpassen.

Das Tolle an der |_+_| Methode ist jedoch, dass wir sie verwenden können, um komponieren Stück für Stück unser Programm gut lesbar. Anstatt zum Beispiel das |_+_| . zu hören Ereignis oben können wir einfach einen Transformationsstream erstellen, um den Fortschritt zu melden, und das |_+_| . ersetzen mit einem anderen anrufen |_+_| Anruf:

fs.readFile

Dieses |_+_| stream ist ein einfacher Pass-Through-Stream, meldet aber auch den Fortschritt an die Standardausgabe. Beachten Sie, wie ich das zweite Argument im |_+_| . verwendet habe Funktion zum Verschieben der Daten in das |_+_| Methode. Dies entspricht dem Pushen der Daten zuerst.

Die Anwendungsmöglichkeiten des Kombinierens von Streams sind endlos. Wenn wir beispielsweise die Datei vor oder nach dem gzip verschlüsseln müssen, müssen wir lediglich einen weiteren Transformationsstream in genau der von uns benötigten Reihenfolge weiterleiten. Wir können Nodes |_+_| . verwenden Modul dazu:

fs.createReadStream

Das obige Skript komprimiert und verschlüsselt dann die übergebene Datei und nur diejenigen, die das Geheimnis haben, können die ausgegebene Datei verwenden. Wir können diese Datei nicht mit den normalen Dienstprogrammen zum Entpacken entpacken, da sie verschlüsselt ist.

Um tatsächlich alles, was mit dem obigen Skript gezippt wurde, entpacken zu können, müssen wir die entgegengesetzten Streams für crypto und zlib in umgekehrter Reihenfolge verwenden, was einfach ist:

fs.createReadStream

Angenommen, die übergebene Datei ist die komprimierte Version, der obige Code erstellt daraus einen Lesestrom und leitet ihn in die Krypto |_+_| stream (mit dem gleichen Geheimnis), leiten Sie die Ausgabe davon in die zlib |_+_| streamen und dann Dinge ohne den Erweiterungsteil zurück in eine Datei schreiben.

Danke fürs Lesen!

#nodejs #node #javascript #webdev #Datenbanken