Node.js I/O

Node.js Streams

Streams verarbeiten Daten in Chunks, anstatt alles in den Speicher zu laden. So kopiert Node Dateien, komprimiert Payloads und leitet HTTP-Bodies effizient weiter.

intermediate15 min readUpdated 15. Sept. 2026
compress.js
js
// compress.js
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import { createGzip } from "node:zlib";

await pipeline(
  createReadStream("access.log"),
  createGzip(),
  createWriteStream("access.log.gz"),
);
Chunk-Größe
highWaterMark
Typen
Readable, Writable, Duplex, Transform
Verbindung
pipe und pipeline
Flusssteuerung
Backpressure
Iteration
for await
Ideal für
Große oder unbegrenzte Datenmengen

Warum es wichtig ist

Warum Streams wichtig sind

Konstanter Speicherverbrauch

Ein Stream hält immer nur einen Chunk gleichzeitig, sodass Gigabytes an Daten mit nur wenigen Megabytes Arbeitsspeicher verarbeitet werden können.

Natürlicher Backpressure

Ein langsamer Konsument signalisiert dem Produzenten, das Tempo zu drosseln, damit schnelle Quellen den Speicher oder eine langsame Festplatte nicht überlasten.

Komponierbare Pipelines

Readable-, Transform- und Writable-Stufen lassen sich einfach kombinieren, wodurch komplexe Verarbeitungsprozesse in lesbare Ketten verwandelt werden.

Das Gesamtbild

Die drei Kernkonzepte von Streams

Daten fließen in Chunks, Backpressure hält Produzent und Konsument im Gleichgewicht und die pipeline verbindet die einzelnen Stufen.

Chunks

Fluss

Daten bewegen sich in Buffern oder Objekten anstatt als ein einziger großer Wert.

Backpressure

Balance

Interne Buffer und die highWaterMark verhindern, dass Produzenten die Konsumenten überholen.

Pipeline

Komposition

pipeline verbindet die Stufen und propagiert Fehler sowie die Bereinigung (Cleanup).

Streams auf einen Blick

Der Kern von Streams

Readable

Eine Quelle, aus der gelesen wird, wie z. B. eine Datei, ein Socket oder ein Request-Body.

Writable

Ein Ziel, in das geschrieben wird, wie z. B. eine Datei oder eine Response.

Duplex

Sowohl readable als auch writable, wie z. B. ein TCP-Socket.

Transform

Eine Duplex-Stufe, die Daten modifiziert, wie z. B. gzip oder ein Parser.

Backpressure

write gibt false zurück und man wartet auf das drain-Event.

Object mode

Streamt Objekte anstatt Bytes für strukturierte Pipelines.

Eine kurze Geschichte

Streams im Wandel der Zeit

  1. 2010

    Einführung von Streams

    Node führt Streams ein, um Daten inkrementell zu verarbeiten.

    10
  2. 2012

    Streams 2

    Eine überarbeitete API fügt pipe und Backpressure hinzu.

    12
  3. 2017

    Streams 3

    Klarere Semantik und pipeline verbessern das Fehlerhandling.

    17
  4. 2018

    Async Iteration

    for await of lässt das Lesen von Streams wie bei Arrays wirken.

    18
  5. Heute

    Überall präsent

    HTTP, Dateien, Kompression, Krypto und viele Bibliotheken basieren auf Streams.

    Heute

Der vollständige Leitfaden

Node.js Streams: Alles was Sie wissen müssen

Warum Streams wichtig sind

Das Laden einer großen Datei in den Arbeitsspeicher funktioniert so lange, bis die Datei größer ist als der verfügbare Speicher. Streams lösen dieses Problem, indem sie Daten in kleinen Stücken (Chunks) verarbeiten: Man liest ein Stück, verarbeitet es und macht dann weiter. So bleibt der Speicherverbrauch weitgehend konstant, unabhängig von der Größe der Eingabedaten.

Streams sind in Node. allgegenwärtig. HTTP-Request- und Response-Bodies, das Lesen und Schreiben von Dateien, Kompression, Verschlüsselung und viele Parser basieren auf Streams. Wenn man sie versteht, kann man Server und Tools bauen, die unbegrenzte Datenmengen verarbeiten, ohne abzustürzen.

Die vier Stream-Typen

Jeder Stream gehört zu einem von vier Typen:

  • Readable — eine Quelle, aus der gelesen wird. Dateien, HTTP request bodies, Sockets und process.stdin.
  • Writable — ein Ziel, in das geschrieben wird. Dateien, HTTP responses, Sockets und process.stdout.
  • Duplex — sowohl readable als auch writable, wie zum Beispiel ein TCP socket.
  • Transform — ein Duplex-Stream, der Daten während der Übertragung modifiziert, wie etwa zlib.createGzip() oder ein CSV-Parser.

Readable- und Writable-Streams werden in Pipelines verbunden, wobei Transforms in der Mitte sitzen.

Lesen und Schreiben

Der einfachste Weg, einen readable stream zu konsumieren, ist die asynchrone Iteration.

// read.js
import { createReadStream } from "node:fs";

const stream = createReadStream("access.log", { encoding: "utf8" });

for await (const chunk of stream) {
  process.stdout.write(chunk);
}

Rufen Sie bei writable streams write() auf und signalisieren Sie das Ende mit end().

// write.js
import { createWriteStream } from "node:fs";

const out = createWriteStream("out.txt");

out.write("first line\n");
out.write("second line\n");
out.end();

Der Stream puffert Schreibvorgänge intern und führt sie effizient aus, sodass Sie die Chunks nicht selbst verwalten müssen.

Piping und Pipelines

Eine Pipeline verbindet einen Readable-Stream, null oder mehr Transform-Streams und einen Writable-Stream.

// compress.js
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import { createGzip } from "node:zlib";

await pipeline(
  createReadStream("access.log"),
  createGzip(),
  createWriteStream("access.log.gz"),
);

Bevorzuge immer pipeline gegenüber manuellem .pipe()-Chaining. pipeline leitet Fehler aus jeder Stufe weiter, zerstört die Streams im Fehlerfall und gibt ein Promise zurück, was die Fehlerbehandlung erheblich vereinfacht. Manuelles .pipe() propagiert keine Fehler, sodass ein Fehler im Upstream die Pipeline hängen lassen und das Ziel offen halten kann.

Backpressure

Streams verfügen über interne Buffer. Wenn Sie Daten schneller schreiben, als das Ziel sie verarbeiten kann, füllt sich der Buffer und write() gibt false zurück.

// backpressure.js
function writeAll(stream, chunks) {
  return new Promise((resolve, reject) => {
    let i = 0;
    const next = () => {
      while (i < chunks.length) {
        const ok = stream.write(chunks[i++]);
        if (!ok) {
          stream.once("drain", next);
          return;
        }
      }
      stream.end(resolve);
    };
    stream.on("error", reject);
    next();
  });
}

Das Warten auf drain, bevor weitere Daten geschrieben werden, ist die Art und Weise, wie ein Producer einen langsamen Consumer berücksichtigt. pipeline und pipe handhaben dies automatisch, was ein weiterer Grund für deren Verwendung ist.

Transform Streams

Ein Transform Stream wendet eine Funktion auf jeden Chunk an. Du kannst deine eigenen erstellen.

// upper.js
import { Transform } from "node:stream";

const upper = new Transform({
  transform(chunk, encoding, callback) {
    callback(null, chunk.toString().toUpperCase());
  },
});

process.stdin.pipe(upper).pipe(process.stdout);

Node liefert nützliche Transforms in node:zlib (gzip, deflate), node:crypto (Ciphers und Hashes) sowie in vielen Bibliotheken, wie zum Beispiel CSV- und JSON-Parsern, mit.

Object mode

Standardmäßig übertragen Streams Bytes. Der Object mode ermöglicht es ihnen, stattdessen JavaScript-Objekte zu übertragen, was ideal für strukturierte Pipelines ist.

// object-mode.js
import { Readable, Transform } from "node:stream";

Readable.from([{ id: 1 }, { id: 2 }])
  .pipe(
    new Transform({
      objectMode: true,
      transform(record, _encoding, callback) {
        callback(null, { ...record, seen: true });
      },
    }),
  )
  .on("data", console.log);

Im Object mode zählt highWaterMark Objekte anstelle von Bytes. Auf diese Weise werden Datenbank-Row-Streams, Log-Prozessoren und ETL-Pipelines implementiert.

Gängige Patterns

  • Datei kopieren: pipeline(createReadStream(src), createWriteStream(dest)).
  • Komprimieren oder verschlüsseln: createGzip() oder einen Cipher-Transform einfügen.
  • HTTP-Response streamen: Eine Datei oder ein Abfrageergebnis direkt an res pipen.
  • Zeilengetrennte Daten parsen: Einen Transform verwenden, der bei Zeilenumbrüchen splittet.
  • Upload-Fortschritt: Bytes in einem Transform während des Durchlaufs zählen.

Best Practices

  • Bevorzuge pipeline gegenüber .pipe() für die Fehlerbehandlung und das Cleanup.
  • Streame große Dateien und Antworten, anstatt sie zu puffern.
  • Beachte Backpressure; ignoriere niemals, wenn write() false zurückgibt.
  • Optimiere highWaterMark erst nach entsprechenden Messungen.
  • Nutze den Object Mode für strukturierte Daten.
  • Behandle error bei jedem Stream, den du erstellst.
  • Zerstöre Streams im Fehlerfall, damit File Descriptors wieder freigegeben werden.

Häufige Fehler

  • Verwendung von readFile für große oder unbegrenzte Datenmengen.
  • Verkettung von .pipe(), wodurch Fehler verloren gehen.
  • Ignorieren von Backpressure und das Puffern unbegrenzter Daten.
  • Vergessen, dass ein Transform-Callback genau einmal aufgerufen werden muss.
  • Mischen von Encodings, was zu korruptem Output führt.
  • Streams nach einem Fehler geöffnet lassen.

Wie geht es weiter?

Streams sind die Art und Weise, wie Node.js Daten in großem Umfang verarbeitet. Setzen Sie dieses Wissen im File System Guide in die Praxis um und verstehen Sie die zugrunde liegende Planung im Event Loop Guide. Schreiben Sie anschließend einen readFile-Aufruf als Stream um und beobachten Sie, wie die Speicherauslastung sinkt.

Verbindung von Stream-Stufen

pipeline handhabt Fehler, schließt jede Stufe und führt das Cleanup durch. Manuelles pipe leitet Fehler nicht weiter, was zu hängenden Prozessen oder Memory Leaks führen kann.

Bevorzugt
import { pipeline } from "node:stream/promises";

await pipeline(source, transform, destination);
Vermeiden
source.pipe(transform).pipe(destination);
// errors on source are not
// forwarded to destination

Lesen einer großen Datei

Ein Stream hält den Speicherverbrauch konstant. readFile puffert die gesamte Datei, was bei kleinen Dateien okay, bei großen jedoch gefährlich ist.

Bevorzugt
import { createReadStream } from "node:fs";

const stream = createReadStream("big.log");
for await (const chunk of stream) {
  handle(chunk);
}
Vermeiden
import { readFile } from "node:fs/promises";

// the whole file in memory
const data = await readFile("big.log");
handle(data);

Häufig gestellte Fragen

Häufig gestellte Fragen

Keep learning

Related topics from the roadmap.

$ Lernen Sie jetzt

Bereit, Streams zu lernen?

Unser interaktives Tutorial führt Sie Schritt für Schritt durch Streams — mit Quizzen und echtem Code, den Sie im Browser ausführen können.