Start · Sprachen · JavaScript · Referenz · ReadableStream

ReadableStream

Schnittstelle

Das Interface <code>ReadableStream</code> der Streams API steht für eine lesbare Datenquelle, aus der Daten nach und nach in Blöcken (Chunks) gelesen werden.

seit JavaScript Baseline: alle modernen Browser (ab 2019/2022 voll Kategorie: webapi

Signatur

interface ReadableStream

Beschreibung

ReadableStream ist das zentrale Interface der Streams API. Es steht für eine Datenquelle, die ihre Daten nicht auf einmal, sondern nach und nach in einzelnen Blöcken (Chunks) liefert. So lassen sich große oder langsam eintreffende Daten wie Netzwerkantworten, Dateien oder Server-Sent-Daten verarbeiten, ohne alles im Speicher zu puffern. Response.body von fetch() und Blob.prototype.stream() liefern zum Beispiel einen ReadableStream.

Einen eigenen Stream erzeugst du mit new ReadableStream(underlyingSource, queuingStrategy). Die Underlying Source legt über die Methoden start, pull und cancel fest, wie Daten in die interne Warteschlange kommen. Mit type: "bytes" entsteht ein Byte-Stream, der „Zero-Copy“-Lesen mit einem ReadableStreamBYOBReader erlaubt.

Gelesen wird über einen Reader (getReader()), mit for await...of (asynchrone Iteration) oder über Pipes: pipeTo() schreibt in einen WritableStream, pipeThrough() leitet durch einen TransformStream, etwa TextDecoderStream oder CompressionStream. Mit tee() teilst du einen Stream in zwei unabhängige Zweige auf.

Wichtige Mitglieder:

  • locked (Eigenschaft) – zeigt an, ob der Stream gerade an einen Reader gebunden ist
  • cancel(reason) – bricht den Stream ab
  • getReader({ mode }) – holt einen Default- oder BYOB-Reader
  • pipeTo(dest, options), pipeThrough(transform, options), tee()
  • ReadableStream.from(iterable) (statisch) – erzeugt einen Stream aus einem (asynchronen) Iterable

Parameter

Name Typ Default Beschreibung
$underlyingSource object {} Legt das Verhalten der Quelle fest: start(controller) wird einmal beim Erzeugen aufgerufen, pull(controller) immer dann, wenn die Warteschlange Nachschub braucht, cancel(reason) beim Abbruch. type: "bytes" erzeugt einen Byte-Stream, autoAllocateChunkSize steuert die automatische Puffergröße.
$queuingStrategy { highWaterMark?: number, size?: (chunk) => number } | CountQueuingStrategy | ByteLengthQueuingStrategy { highWaterMark: 1 } (bei Byte-Streams 0) Steuert den Gegendruck (Backpressure): highWaterMark ist die gewünschte Füllhöhe der Warteschlange, size berechnet die Größe eines Chunks. Bei type: "bytes" darf size nicht angegeben werden.

Rückgabewert

Typ
ReadableStream
Beschreibung
Eine neue ReadableStream-Instanz, die über einen Reader, eine Pipe oder for await...of gelesen werden kann.

Beispiele

Eigenen Stream erzeugen und mit einem Reader lesen

const stream = new ReadableStream({
  start(controller) {
    controller.enqueue('Hallo');
    controller.enqueue('Streams');
    controller.close();
  }
});

const reader = stream.getReader();
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  console.log(value);
}
reader.releaseLock();
Hallo Streams

fetch-Antwort beim Download mitlesen (Fortschritt)

const response = await fetch('/grosse-datei.json');
const total = Number(response.headers.get('Content-Length')) || 0;
const reader = response.body.getReader();
let received = 0;
const chunks = [];

while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  chunks.push(value);
  received += value.byteLength;
  if (total) console.log(`Fortschritt: ${Math.round((received / total) * 100)} %`);
}

const text = new TextDecoder().decode(await new Blob(chunks).arrayBuffer());
console.log('Fertig, Zeichen:', text.length);
Fortschritt: 12 % ... Fortschritt: 100 % Fertig, Zeichen: 523411

Text mit pipeThrough dekodieren und mit for await iterieren

const response = await fetch('/log.txt');
const textStream = response.body.pipeThrough(new TextDecoderStream());

for await (const chunk of textStream) {
  console.log('Block empfangen:', chunk.length, 'Zeichen');
}
Block empfangen: 16384 Zeichen Block empfangen: 3021 Zeichen

Pull-basierte Quelle mit Backpressure und Abbruch

let n = 0;
const zahlen = new ReadableStream({
  pull(controller) {
    // wird nur aufgerufen, wenn der Konsument Daten braucht
    controller.enqueue(n++);
    if (n > 1000) controller.close();
  },
  cancel(reason) {
    console.log('Abgebrochen:', reason);
  }
}, { highWaterMark: 5 });

const reader = zahlen.getReader();
console.log((await reader.read()).value);
console.log((await reader.read()).value);
await reader.cancel('genug gelesen');
0 1 Abgebrochen: genug gelesen

// Wichtig · Fallstricke

  • Sperre (Lock): Solange ein Reader aktiv ist, gilt locked === true. Ein zweites getReader(), pipeTo() oder tee() wirft dann einen TypeError. Gib die Sperre mit reader.releaseLock() frei.
  • Nur einmal lesbar: Ein Stream lässt sich nur ein einziges Mal auslesen. Response.body ist nach response.json() verbraucht. Brauchst du die Daten zweimal, nutze tee() (Vorsicht: Der langsamere Zweig puffert alles im Speicher) oder response.clone().
  • Asynchrone Iteration (for await...of) wird von Safari erst seit Version 18 unterstützt, ReadableStream.from() ist ebenfalls noch relativ neu. Prüfe bei Bedarf mit Feature-Detection oder setze ein Polyfill ein.
  • for await bricht den Stream bei break automatisch ab. Mit stream.values({ preventCancel: true }) bleibt er offen.
  • Chunks von fetch sind Uint8Arrays mit beliebigen Grenzen. Mehrbyte-UTF-8-Zeichen können auf zwei Chunks verteilt sein, deshalb TextDecoderStream oder TextDecoder mit { stream: true } verwenden.
  • Fehler in start/pull oder controller.error() versetzen den Stream in den Fehlerzustand. Ab dann schlagen alle weiteren read()-Aufrufe fehl.
  • In Node.js gibt es neben den Web Streams (node:stream/web, ab v18 global) die älteren Node-Streams. Umwandeln kannst du mit Readable.fromWeb() und Readable.toWeb().