Start · Sprachen · JavaScript · Referenz · new ReadableStream()

new ReadableStream()

Methode

Erzeugt ein neues <code>ReadableStream</code>-Objekt, dessen Daten von den übergebenen Quell-Handlern (<code>start</code>, <code>pull</code>, <code>cancel</code>) geliefert werden.

seit JavaScript Baseline: alle modernen Browser (seit 2019/2022), Kategorie: webapi

Signatur

new ReadableStream(underlyingSource, queuingStrategy)

Beschreibung

Mit dem Konstruktor new ReadableStream() baust du eine eigene lesbare Datenquelle im Sinne der Streams API. Du übergibst ein Objekt mit Handlern (die underlying source). Diese legen fest, wie Datenstücke (chunks) in die interne Warteschlange des Streams kommen. Wer den Stream nutzt, liest die Daten dann über einen Reader, über for await...of oder über pipeTo()/pipeThrough() aus.

Die wichtigsten Handler sind:

  • start(controller) wird sofort beim Erzeugen aufgerufen. Er eignet sich für die Einrichtung oder für Push-Quellen wie WebSockets oder Ereignisse, die von sich aus Daten liefern.
  • pull(controller) wird immer dann aufgerufen, wenn in der Warteschlange Platz ist. Er eignet sich für Pull-Quellen, also Daten, die man auf Anfrage erzeugt oder lädt. So bekommst du automatisch Gegendruck (Backpressure): Die Quelle liefert nur so schnell, wie der Leser verarbeitet.
  • cancel(reason) wird aufgerufen, wenn der Leser den Stream abbricht. Dort gibst du Ressourcen frei.

Mit type: "bytes" entsteht ein Byte-Stream mit einem ReadableByteStreamController. Er erlaubt BYOB-Reader (bring your own buffer), die ohne zusätzliches Kopieren lesen. Ohne type nimmt der Stream beliebige JavaScript-Werte als Chunks an.

Typische Einsatzfälle sind das Streamen einer fetch-Antwort mit selbst erzeugtem Inhalt, zum Beispiel über new Response(stream), das Einpacken von Ereignisquellen in einen Stream und Pipelines mit TransformStream, etwa für Kompression oder Textdekodierung.

Parameter

Name Typ Default Beschreibung
$underlyingSource object {} Objekt mit optionalen Einträgen: start(controller), pull(controller), cancel(reason), type (nur "bytes" erlaubt) und autoAllocateChunkSize (positive Ganzzahl, nur bei Byte-Streams). start und pull dürfen ein Promise zurückgeben. Solange es nicht erfüllt ist, ruft der Stream pull nicht erneut auf.
$queuingStrategy object | CountQueuingStrategy | ByteLengthQueuingStrategy { highWaterMark: 1 } (bei Byte-Streams: 0) Legt die Warteschlangen-Strategie fest: highWaterMark ist die Füllmenge, ab der kein pull mehr ausgelöst wird. size(chunk) berechnet die Größe eines Chunks und ist bei Byte-Streams nicht erlaubt.

Rückgabewert

Typ
ReadableStream
Beschreibung
Ein neues ReadableStream-Objekt, das die Daten aus der angegebenen Quelle liefert.

Beispiele

Einfacher Stream mit start() und Reader

const stream = new ReadableStream({
  start(controller) {
    controller.enqueue('Hallo');
    controller.enqueue('Welt');
    controller.close(); // keine weiteren Daten
  }
});

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

Pull-Quelle mit Gegendruck und for await...of

// Erzeugt Zahlen nur, wenn der Leser sie auch anfordert
let i = 0;
const zahlen = new ReadableStream({
  pull(controller) {
    if (i >= 3) {
      controller.close();
      return;
    }
    controller.enqueue(i++);
  },
  cancel(reason) {
    console.log('Abgebrochen:', reason);
  }
}, { highWaterMark: 2 });

// ReadableStream ist async-iterierbar (Node.js, Firefox, Chrome 124+)
for await (const n of zahlen) {
  console.log('Zahl', n);
}
Zahl 0 Zahl 1 Zahl 2

Selbst erzeugten Text als fetch-Response streamen

const encoder = new TextEncoder();
const zeilen = ['Zeile 1\n', 'Zeile 2\n', 'Zeile 3\n'];

const stream = new ReadableStream({
  async pull(controller) {
    const zeile = zeilen.shift();
    if (zeile === undefined) return controller.close();
    await new Promise(r => setTimeout(r, 100)); // simulierte Verzögerung
    controller.enqueue(encoder.encode(zeile));
  }
});

// z. B. in einem Service Worker: event.respondWith(response)
const response = new Response(stream, {
  headers: { 'Content-Type': 'text/plain; charset=utf-8' }
});
console.log(await response.text());
Zeile 1 Zeile 2 Zeile 3

Ereignisse (Push-Quelle) in einen Stream einpacken

const klickStream = (element) => {
  let handler;
  return new ReadableStream({
    start(controller) {
      handler = (e) => controller.enqueue({ x: e.clientX, y: e.clientY });
      element.addEventListener('click', handler);
    },
    cancel() {
      // Aufräumen, wenn der Leser abbricht
      element.removeEventListener('click', handler);
    }
  });
};

const reader = klickStream(document.body).getReader();
const { value } = await reader.read();
console.log('Erster Klick bei', value);
await reader.cancel('genug');
Erster Klick bei { x: 120, y: 45 }

// Wichtig · Fallstricke

  • Gegendruck bei Push-Quellen: Wenn du in start ungebremst enqueue() aufrufst, wird der Gegendruck nicht beachtet und die Warteschlange kann ohne Grenze wachsen. Prüfe dann controller.desiredSize (≤ 0 bedeutet: Quelle pausieren) oder verwende besser pull.
  • Gesperrter Stream: Nach getReader() ist der Stream gesperrt (stream.locked === true). Ein zweiter Reader oder pipeTo() führt dann zu einem TypeError. Gib die Sperre mit reader.releaseLock() frei. Wenn mehrere Leser dieselben Daten brauchen, nutze stream.tee().
  • Fehler: Wirft start oder pull eine Ausnahme oder wird ihr Promise abgelehnt, geht der Stream in den Fehlerzustand über. Du kannst auch selbst controller.error(e) aufrufen. Nach close() führt ein weiteres enqueue() zu einem TypeError.
  • Byte-Streams: Mit type: "bytes" darfst du nur ArrayBufferViews (z. B. Uint8Array) einreihen. Außerdem darf queuingStrategy.size nicht gesetzt sein, sonst gibt es einen RangeError. Byte-Streams unterstützt Safari erst seit Version 18.4 (bitte gegenprüfen), deshalb solltest du vor der Nutzung prüfen, ob die Funktion vorhanden ist.
  • Kompatibilität: Der Konstruktor ist in allen modernen Browsern verfügbar, in Node.js global ab Version 18 (vorher über stream/web). Async-Iteration (for await...of) über ReadableStream gibt es in Chrome/Edge ab Version 124, in Firefox und Node.js schon länger. Safari unterstützt sie noch nicht. Als Ausweichlösung verwendest du dort einen Reader.
  • Einen Stream als Anfrage-Body zu senden (fetch(url, { body: stream, duplex: 'half' })) funktioniert bisher nur in Chromium-Browsern und nur über HTTP/2 oder neuer.