Start · Sprachen · JavaScript · Referenz · TransformStreamDefaultController

TransformStreamDefaultController

Schnittstelle

Controller-Objekt eines <code>TransformStream</code>, mit dem die Callbacks <code>start</code>, <code>transform</code> und <code>flush</code> Daten in die lesbare Seite einreihen, Fehler auslösen oder den Stream beenden.

seit JavaScript Baseline 2022: alle modernen Browser, Node.js 18+ Kategorie: webapi

Signatur

interface TransformStreamDefaultController

Beschreibung

Ein TransformStreamDefaultController wird nie direkt erzeugt. Du bekommst ihn als Argument in den Callbacks start(controller), transform(chunk, controller) und flush(controller) des Transformer-Objekts, das du an new TransformStream() übergibst. Über ihn steuerst du die lesbare Seite (readable) des Streams und damit indirekt auch die beschreibbare Seite (writable).

Das Interface hat diese Mitglieder:

  • enqueue(chunk): reiht einen Chunk in die lesbare Seite ein. Du kannst die Methode pro Eingabe-Chunk beliebig oft aufrufen (0‑mal zum Filtern, mehrmals zum Aufteilen).
  • error(reason): versetzt beide Seiten in den Fehlerzustand. Wartende Lese- und Schreibvorgänge werden mit reason abgelehnt.
  • terminate(): schließt die lesbare Seite und setzt die beschreibbare Seite in einen Fehlerzustand (TypeError). Das ist nützlich, wenn du nach einer bestimmten Datenmenge aufhören willst.
  • desiredSize (nur lesbar): zeigt, wie viel Platz noch in der internen Warteschlange der lesbaren Seite ist. Der Wert hängt von der highWaterMark ab und ist negativ, wenn die Warteschlange zu voll ist. Nach einem Fehler ist er null.

Typische Einsatzfälle sind Pipeline-Stufen in pipeThrough(): Text zeilenweise zerlegen, JSON-Zeilen (NDJSON) parsen, Daten filtern oder zählen, Chunks umwandeln oder Streams nach einem Limit abbrechen.

Parameter

Name Typ Default Beschreibung
$chunk any undefined Parameter von enqueue(chunk): der Wert, der an die lesbare Seite weitergegeben wird.
$reason any undefined Parameter von error(reason): der Fehlergrund, meist ein Error-Objekt. Er wird an alle Leser und Schreiber weitergegeben.

Beispiele

Text in Großbuchstaben umwandeln

const upper = new TransformStream({
  transform(chunk, controller) {
    controller.enqueue(chunk.toUpperCase());
  }
});

const source = ReadableStream.from(['hallo ', 'welt']);
const result = await new Response(
  source.pipeThrough(upper).pipeThrough(new TextEncoderStream())
).text();
console.log(result);
HALLO WELT

Zeilen aufteilen mit Puffer und flush()

const lineSplitter = () => {
  let buffer = '';
  return new TransformStream({
    transform(chunk, controller) {
      buffer += chunk;
      const lines = buffer.split('\n');
      buffer = lines.pop(); // unvollständige Zeile aufheben
      for (const line of lines) controller.enqueue(line);
    },
    flush(controller) {
      if (buffer) controller.enqueue(buffer);
    }
  });
};

const res = await fetch('/api/log.ndjson');
const lines = res.body
  .pipeThrough(new TextDecoderStream())
  .pipeThrough(lineSplitter());

for await (const line of lines) {
  console.log(JSON.parse(line));
}
{ id: 1, ... } { id: 2, ... }

Nach einem Limit abbrechen mit terminate()

const take = (n) => {
  let count = 0;
  return new TransformStream({
    transform(chunk, controller) {
      controller.enqueue(chunk);
      if (++count >= n) controller.terminate();
    }
  });
};

const numbers = ReadableStream.from([1, 2, 3, 4, 5]);
for await (const n of numbers.pipeThrough(take(3))) {
  console.log(n);
}
1 2 3

Ungültige Daten mit error() melden

const validate = new TransformStream({
  transform(chunk, controller) {
    if (typeof chunk !== 'number') {
      controller.error(new TypeError(`Keine Zahl: ${chunk}`));
      return;
    }
    console.log('desiredSize:', controller.desiredSize);
    controller.enqueue(chunk * 2);
  }
});

try {
  for await (const v of ReadableStream.from([1, 'x', 3]).pipeThrough(validate)) {
    console.log(v);
  }
} catch (err) {
  console.error(err.message);
}
desiredSize: 0 2 Keine Zahl: x

// Wichtig · Fallstricke

  • Kein Konstruktor: new TransformStreamDefaultController() wirft einen TypeError. Du bekommst den Controller nur über die Transformer-Callbacks.
  • Nach dem Schließen: Wenn du enqueue() nach terminate() oder error() aufrufst, wird ein TypeError geworfen. Prüfe den Zustand oder beende den Callback danach mit return.
  • Gegendruck (Backpressure): enqueue() wartet nicht, selbst wenn desiredSize <= 0 ist. Der Stream bremst stattdessen die beschreibbare Seite. Bei sehr großen Datenmengen pro Chunk kann die Warteschlange trotzdem stark wachsen.
  • Asynchrone Transformation: Gibt transform() ein Promise zurück, wird der nächste Chunk erst verarbeitet, wenn es erfüllt ist. Wird das Promise abgelehnt, ist das genauso, als würdest du controller.error() aufrufen.
  • Ohne transform-Callback reicht der Stream jeden Chunk unverändert durch (Identitäts-Transformation).
  • Kompatibilität: Wird von allen aktuellen Browsern unterstützt, auch in Workern. In Node.js steht die Schnittstelle global ab Version 18 zur Verfügung (vorher über stream/web). ReadableStream.from() und for await über Streams in den Beispielen sind neuer. In Safari fehlt das asynchrone Iterieren teilweise. Dort kannst du alternativ getReader() verwenden.