Start · Sprachen · JavaScript · Referenz · ReadableStreamDefaultController

ReadableStreamDefaultController

Schnittstelle

Controller eines <code>ReadableStream</code>, mit dem die Datenquelle Chunks in die interne Warteschlange einreiht, den Stream schließt oder mit einem Fehler abbricht.

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

Signatur

interface ReadableStreamDefaultController

Beschreibung

Das Interface ReadableStreamDefaultController gehört zur Streams API. Eine Instanz lässt sich nicht selbst erzeugen: Der Konstruktor new ReadableStream(underlyingSource) legt den Controller automatisch an und übergibt ihn an die Callbacks start(controller) und pull(controller) der zugrunde liegenden Quelle.

Über den Controller steuert die Datenquelle den Stream:

  • controller.enqueue(chunk): reiht einen Chunk in die interne Warteschlange ein.
  • controller.close(): schließt den Stream. Bereits eingereihte Chunks können danach noch gelesen werden.
  • controller.error(e): versetzt den Stream in den Fehlerzustand. Alle ausstehenden und künftigen Lesevorgänge werden dann mit e abgelehnt.
  • controller.desiredSize (nur lesbar): gibt an, wie viel Platz noch in der Warteschlange ist, gemessen an der highWaterMark der Queuing-Strategie. Diese Angabe ist die Grundlage für Gegendruck (Backpressure).

Du brauchst den Controller immer dann, wenn du eigene Datenquellen als Stream bereitstellen willst, etwa Events, WebSocket-Nachrichten, erzeugte Daten oder zerlegte Dateien. Bei reinen Byte-Quellen mit Zero-Copy-Unterstützung (type: "bytes") bekommst du stattdessen einen ReadableByteStreamController.

Man unterscheidet zwei Arten von Quellen. Eine Push-Quelle ruft enqueue() selbstständig auf, sobald Daten da sind, und sollte dabei desiredSize beachten. Eine Pull-Quelle liefert Daten nur in pull(). Diese Methode ruft der Stream automatisch auf, sobald desiredSize > 0 ist.

Parameter

Name Typ Default Beschreibung
$chunk any Parameter von enqueue(chunk): beliebiger Wert, der in die Warteschlange eingereiht wird, z. B. ein String, ein Uint8Array oder ein Objekt.
$e any undefined Parameter von error(e): der Fehlergrund, mit dem Leser abgewiesen werden. Am besten übergibst du ein Error-Objekt.

Beispiele

Stream in start() mit festen Daten füllen und lesen

const stream = new ReadableStream({
  start(controller) {
    ['Hallo', ' ', 'Welt'].forEach((teil) => controller.enqueue(teil));
    controller.close(); // eingereihte Chunks bleiben lesbar
  }
});

const leseAlles = async () => {
  let text = '';
  for await (const chunk of stream) text += chunk;
  console.log(text);
};
leseAlles();
Hallo Welt

Pull-Quelle mit Gegendruck: Zähler, der nur bei Bedarf liefert

let n = 0;
const zaehler = new ReadableStream({
  pull(controller) {
    // pull wird nur aufgerufen, wenn desiredSize > 0 ist
    n += 1;
    controller.enqueue(n);
    if (n === 3) controller.close();
  }
}, { highWaterMark: 1 });

const reader = zaehler.getReader();
const lies = async () => {
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    console.log('Wert:', value);
  }
};
lies();
Wert: 1 Wert: 2 Wert: 3

Push-Quelle aus Events mit desiredSize und error()

const ticker = new ReadableStream({
  start(controller) {
    let i = 0;
    this.timer = setInterval(() => {
      i += 1;
      if (controller.desiredSize <= 0) {
        console.log('Warteschlange voll, Tick verworfen');
        return;
      }
      controller.enqueue(`Tick ${i}`);
      if (i === 5) {
        clearInterval(this.timer);
        controller.error(new Error('Quelle getrennt'));
      }
    }, 100);
  },
  cancel(reason) {
    clearInterval(this.timer); // Aufräumen bei reader.cancel()
    console.log('Abgebrochen:', reason);
  }
}, { highWaterMark: 10 });

const lesen = async () => {
  try {
    for await (const t of ticker) console.log(t);
  } catch (err) {
    console.error('Stream-Fehler:', err.message);
  }
};
lesen();
Tick 1 Tick 2 Tick 3 Tick 4 Stream-Fehler: Quelle getrennt

// Wichtig · Fallstricke

  • Kein eigener Konstruktor: new ReadableStreamDefaultController() wirft einen TypeError. Den Controller bekommst du nur über die Callbacks von ReadableStream.
  • Nach close() oder error() wirft ein weiterer Aufruf von enqueue() oder close() einen TypeError. Prüfe deshalb den eigenen Zustand oder desiredSize !== null, bevor du nach dem Schließen weitere Daten einreihst.
  • error() verwirft die Warteschlange: Anders als bei close() gehen Chunks, die noch nicht gelesen wurden, sofort verloren. In Beispiel 3 kommt deshalb Tick 5 nicht mehr an.
  • Gegendruck wird nicht erzwungen: enqueue() funktioniert auch bei desiredSize <= 0. Beachtet die Quelle den Wert nicht, wächst der Speicherverbrauch unbegrenzt.
  • pull() und Promises: Gibt pull() ein Promise zurück, ruft der Stream pull() erst wieder auf, wenn dieses Promise erfüllt ist. Wird es abgelehnt, geht der Stream in den Fehlerzustand.
  • Kompatibilität: Der Controller wird in allen modernen Browsern unterstützt. Die asynchrone Iteration mit for await über einen ReadableStream ist jünger; Safari unterstützt sie erst spät. In Node.js ist der Controller unter stream/web verfügbar, ab Version 18 auch global.