Start · Sprachen · JavaScript · Referenz · TextDecoderStream.readable

TextDecoderStream.readable

Eigenschaft

Schreibgeschützte Eigenschaft, die die lesbare Seite eines <code>TextDecoderStream</code> liefert – einen <code>ReadableStream</code>, der dekodierte Strings ausgibt.

seit JavaScript Baseline 2023: alle modernen Browser (Chrome 71+, Kategorie: webapi

Signatur

TextDecoderStream.readable

Beschreibung

Ein TextDecoderStream ist ein Transform-Stream: In seine beschreibbare Seite (writable) werden Byte-Blöcke (Uint8Array, ArrayBuffer oder andere BufferSource) geschrieben, und die Eigenschaft readable stellt die zugehörige Ausgabeseite bereit. Aus diesem ReadableStream liest man die dekodierten Textstücke als string.

Typischerweise greift man nicht direkt auf readable zu, sondern übergibt den ganzen TextDecoderStream an ReadableStream.prototype.pipeThrough(). Das Ergebnis dieses Aufrufs ist genau dieser readable-Stream. Direkt verwenden muss man die Eigenschaft, wenn man Bytes manuell über writable.getWriter() einspeist oder die Ausgabe gezielt an andere Stellen weiterleitet (pipeTo(), tee()).

Wichtig: Die Chunk-Grenzen der Ausgabe entsprechen nicht Zeilen oder logischen Einheiten. Der Decoder puffert lediglich unvollständige Mehrbyte-Zeichen (z. B. UTF-8-Umlaute, die über zwei Byte-Blöcke verteilt sind) und gibt sie korrekt zusammengesetzt aus. Wer Zeilen braucht, muss selbst puffern und splitten.

Rückgabewert

Typ
ReadableStream<string>
Beschreibung
Die lesbare Seite des Transform-Streams. Jeder gelesene Chunk ist ein string mit dekodiertem Text. Es wird bei jedem Zugriff dasselbe Objekt zurückgegeben.

Beispiele

Fetch-Antwort als Text-Stream lesen

const response = await fetch('/api/log.txt');

// pipeThrough() liefert decoder.readable zurück
const textStream = response.body.pipeThrough(new TextDecoderStream());

let gesamt = '';
for await (const chunk of textStream) {
  gesamt += chunk;
  console.log(`Chunk mit ${chunk.length} Zeichen empfangen`);
}
console.log('Fertig:', gesamt.length, 'Zeichen');
Chunk mit 16384 Zeichen empfangen Chunk mit 4210 Zeichen empfangen Fertig: 20594 Zeichen

Bytes manuell schreiben und über readable lesen

const decoder = new TextDecoderStream('utf-8');
const writer = decoder.writable.getWriter();
const reader = decoder.readable.getReader();

// „ü“ = 0xC3 0xBC – absichtlich auf zwei Chunks verteilt
writer.write(new Uint8Array([0x47, 0x72, 0xC3]));
writer.write(new Uint8Array([0xBC, 0xDF, 0x65]));
writer.close();

let text = '';
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  text += value;
}
console.log(text);
Grüße

Zeilenweise Verarbeitung eines Streams (z. B. NDJSON)

const zeilenSplitter = () => {
  let rest = '';
  return new TransformStream({
    transform(chunk, controller) {
      const teile = (rest + chunk).split('\n');
      rest = teile.pop();
      teile.forEach((z) => controller.enqueue(z));
    },
    flush(controller) {
      if (rest) controller.enqueue(rest);
    }
  });
};

const response = await fetch('/api/events.ndjson');
const zeilen = response.body
  .pipeThrough(new TextDecoderStream())
  .pipeThrough(zeilenSplitter());

for await (const zeile of zeilen) {
  const event = JSON.parse(zeile);
  console.log(event.type);
}
login click logout

Ausgabe verzweigen mit tee()

const decoder = new TextDecoderStream();
const [fuerLog, fuerUI] = decoder.readable.tee();

const response = await fetch('/readme.txt');
response.body.pipeTo(decoder.writable);

const leseAlles = async (stream) => {
  let s = '';
  for await (const c of stream) s += c;
  return s;
};

const [a, b] = await Promise.all([leseAlles(fuerLog), leseAlles(fuerUI)]);
console.log(a === b);
true

// Wichtig · Fallstricke

  • Schreibgeschützt: Eine Zuweisung an readable wird ignoriert (im Strict Mode TypeError).
  • Sperre: Nach getReader() oder pipeTo() ist der Stream gesperrt (locked === true); ein zweiter Leser wirft einen TypeError. Mit reader.releaseLock() freigeben oder vorher tee() verwenden.
  • Chunk-Grenzen sind willkürlich: Ein Chunk kann mitten in einer Zeile oder einem JSON-Objekt enden – nie einzelne Chunks direkt parsen.
  • Backpressure: Wird readable nicht gelesen, füllt sich die interne Warteschlange und writer.write()/writer.ready bleiben hängen. Beim manuellen Schreiben nicht auf jedes write() warten, bevor gelesen wird, sonst droht ein Deadlock.
  • Fehlerhafte Bytes: Standardmäßig werden ungültige Sequenzen durch U+FFFD (�) ersetzt. Mit new TextDecoderStream('utf-8', { fatal: true }) gerät readable stattdessen in den Fehlerzustand (TypeError).
  • Async-Iteration: for await...of über einen ReadableStream wird in Safari erst ab Version 18.x (bzw. teils noch nicht) unterstützt; für breite Kompatibilität getReader() mit read()-Schleife nutzen.
  • Kompatibilität: Firefox unterstützt TextDecoderStream erst ab Version 105. In Node.js ist die Klasse seit 16.6 global, vorher über require('node:stream/web').