Start · Sprachen · JavaScript · Referenz · ReadableStreamDefaultReader

ReadableStreamDefaultReader

Schnittstelle

Standardleser der Streams-API, der Chunks aus einem <code>ReadableStream</code> (z. B. einem Fetch-<code>Response.body</code>) nacheinander per <code>read()</code> liest.

seit JavaScript Baseline: alle modernen Browser (seit 2019), Node. Kategorie: webapi

Signatur

interface ReadableStreamDefaultReader

Beschreibung

ReadableStreamDefaultReader ist der Standardleser der Streams-API. Ihr bekommt ihn meist über stream.getReader() oder erzeugt ihn direkt mit new ReadableStreamDefaultReader(stream). Solange ein Leser aktiv ist, ist der Stream gesperrt (stream.locked === true). Nur dieser Leser kann dann Daten entnehmen.

Die zentrale Methode ist read(). Sie gibt ein Promise zurück, das mit einem Objekt { value, done } erfüllt wird. value ist der nächste Chunk, bei Fetch-Antworten ein Uint8Array. done ist true, sobald der Stream beendet ist. Typische Einsatzfälle sind: große Downloads Stück für Stück verarbeiten, Fortschrittsanzeigen, Server-gesendete Datenströme wie NDJSON- oder LLM-Token-Streams lesen oder Daten parsen, bevor die ganze Antwort da ist.

Weitere Mitglieder:

  • closed: ein Promise, das erfüllt wird, wenn der Stream geschlossen wird. Bei einem Fehler oder beim Freigeben der Sperre wird es abgelehnt.
  • cancel(reason): bricht den Stream ab. Die Quelle wird informiert, und ungelesene Daten werden verworfen.
  • releaseLock(): gibt die Sperre frei, damit ein anderer Leser den Stream nutzen kann.

Für Byte-Streams mit eigenen Puffern („Bring Your Own Buffer“) gibt es zusätzlich ReadableStreamBYOBReader. Für die meisten Fälle reicht der Standardleser.

Parameter

Name Typ Default Beschreibung
$stream Pflicht ReadableStream Nur für den Konstruktor new ReadableStreamDefaultReader(stream): der Stream, der gelesen werden soll. Ist er schon gesperrt, wird ein TypeError geworfen.
$reason any undefined Für cancel(reason): ein Abbruchgrund, der an die zugrunde liegende Quelle weitergegeben wird.

Rückgabewert

Typ
ReadableStreamDefaultReader
Beschreibung
Der Konstruktor und getReader() geben eine Leser-Instanz zurück. read() gibt Promise<{ value: any, done: boolean }> zurück, cancel() gibt Promise<void> zurück, closed ist ein Promise<void>.

Beispiele

Fetch-Antwort Stück für Stück lesen, mit Fortschritt

const downloadWithProgress = async (url) => {
  const response = await fetch(url);
  const total = Number(response.headers.get('Content-Length')) || 0;
  const reader = response.body.getReader();
  const chunks = [];
  let received = 0;

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

  return new Blob(chunks);
};

downloadWithProgress('/datei.zip').then((blob) => console.log('Fertig:', blob.size, 'Bytes'));
Fortschritt: 12 % Fortschritt: 47 % Fortschritt: 100 % Fertig: 1048576 Bytes

Leser per Konstruktor erzeugen und eigenen Stream lesen

const stream = new ReadableStream({
  start(controller) {
    ['a', 'b', 'c'].forEach((c) => controller.enqueue(c));
    controller.close();
  }
});

const reader = new ReadableStreamDefaultReader(stream);
console.log(stream.locked);

reader.closed.then(() => console.log('Stream geschlossen'));

const readAll = async () => {
  let result;
  while (!(result = await reader.read()).done) {
    console.log('Chunk:', result.value);
  }
};
readAll();
true Chunk: a Chunk: b Chunk: c Stream geschlossen

Text-Stream dekodieren und zeilenweise verarbeiten (NDJSON)

const readNdjson = async (url, onItem) => {
  const response = await fetch(url);
  const reader = response.body
    .pipeThrough(new TextDecoderStream())
    .getReader();
  let buffer = '';

  try {
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;
      buffer += value;
      const lines = buffer.split('\n');
      buffer = lines.pop(); // unvollständige Zeile aufheben
      lines.filter(Boolean).forEach((line) => onItem(JSON.parse(line)));
    }
    if (buffer.trim()) onItem(JSON.parse(buffer));
  } finally {
    reader.releaseLock();
  }
};

readNdjson('/api/events', (item) => console.log(item));
{ id: 1, type: 'start' } { id: 2, type: 'update' }

Lesen nach Zeitlimit abbrechen

const readWithTimeout = async (url, ms) => {
  const response = await fetch(url);
  const reader = response.body.getReader();
  const timer = setTimeout(() => reader.cancel('Zeitlimit erreicht'), ms);
  let bytes = 0;

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    bytes += value.byteLength;
  }
  clearTimeout(timer);
  return bytes;
};

readWithTimeout('/stream', 2000).then((n) => console.log('Gelesen:', n, 'Bytes'));
Gelesen: 20480 Bytes

// Wichtig · Fallstricke

  • Sperre: Solange ein Leser existiert, schlagen getReader(), pipeTo() und response.text() fehl, weil der Stream gesperrt ist. Gebt die Sperre mit releaseLock() frei, am besten in einem finally-Block. Offene read()-Promises werden dabei abgelehnt; ältere Implementierungen werfen stattdessen einen Fehler.
  • Einmal lesbar: Ein Fetch-Body lässt sich nur einmal verbrauchen. Wenn ihr ihn zweimal braucht, nutzt vorher response.clone() oder stream.tee().
  • Chunk-Grenzen: Die Chunk-Größe ist nicht festgelegt. Ein UTF-8-Zeichen oder eine JSON-Zeile kann auf zwei Chunks verteilt sein. Dekodiert daher mit TextDecoderStream oder TextDecoder mit { stream: true } und puffert unvollständige Zeilen.
  • Fehler: Tritt im Stream ein Fehler auf, wird read() abgelehnt. Fangt das mit try/catch ab.
  • Alternative: In modernen Umgebungen (Node.js, Firefox, Chrome 124+) ist ReadableStream asynchron iterierbar (for await (const chunk of stream)). Das holt und löst den Leser automatisch, ist aber in Safari erst seit Kurzem verfügbar.
  • Kompatibilität: In allen modernen Browsern verfügbar. Der Konstruktor new ReadableStreamDefaultReader() wird etwas später unterstützt als getReader(). Nehmt im Zweifel getReader(). Der Internet Explorer wird nicht unterstützt.