Signatur
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 mitreasonabgelehnt.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 derhighWaterMarkab und ist negativ, wenn die Warteschlange zu voll ist. Nach einem Fehler ist ernull.
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);
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));
}
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);
}
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);
}
// Wichtig · Fallstricke
- Kein Konstruktor:
new TransformStreamDefaultController()wirft einenTypeError. Du bekommst den Controller nur über die Transformer-Callbacks. - Nach dem Schließen: Wenn du
enqueue()nachterminate()odererror()aufrufst, wird einTypeErrorgeworfen. Prüfe den Zustand oder beende den Callback danach mitreturn. - Gegendruck (Backpressure):
enqueue()wartet nicht, selbst wenndesiredSize <= 0ist. 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 ducontroller.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()undfor awaitüber Streams in den Beispielen sind neuer. In Safari fehlt das asynchrone Iterieren teilweise. Dort kannst du alternativgetReader()verwenden.