Start · Sprachen · JavaScript · Referenz · Response.textStream()

Response.textStream()

Methode

Gibt einen <code>ReadableStream</code> zurück, der den Response-Body als Folge von UTF-8-dekodierten Text-Abschnitten (Strings) liefert.

seit JavaScript Experimentell – noch keine Baseline, vor dem Einsa Kategorie: webapi

Signatur

Response.prototype.textStream()

Beschreibung

Die Methode textStream() des Interfaces Response liefert den Body einer Antwort als ReadableStream<string>. Anders als Response.text(), das erst nach dem vollständigen Empfang des Bodys einen einzigen String liefert, bekommt man hier den Text stückweise, sobald er ankommt. Die Bytes werden dabei als UTF-8 dekodiert. Multibyte-Zeichen, die über eine Chunk-Grenze verteilt sind, setzt die Methode korrekt zusammen.

Das ist nützlich bei großen Textantworten (Logdateien, CSV, NDJSON), bei Streaming-Antworten wie Chat- oder KI-Ausgaben und immer dann, wenn man Inhalte schon während des Ladens anzeigen oder verarbeiten will, ohne den ganzen Body im Speicher zu halten.

Inhaltlich entspricht die Methode dem bekannten Muster response.body.pipeThrough(new TextDecoderStream()). Sie ist nur kürzer und fängt den Fall ab, dass kein Body vorhanden ist. Wie bei allen Body-Methoden gilt: Der Body lässt sich nur einmal lesen. Danach ist bodyUsed gleich true, und weitere Aufrufe von text(), json() usw. schlagen fehl.

Rückgabewert

Typ
ReadableStream<string>
Beschreibung
Ein ReadableStream, dessen Abschnitte (Chunks) UTF-8-dekodierte Strings sind. Die Größe der Abschnitte hängt vom Netzwerk ab und ist nicht festgelegt. Ist der Body bereits verbraucht oder gesperrt, wird ein TypeError ausgelöst.

Beispiele

Streaming-Text schrittweise anzeigen (mit Reader)

const output = document.querySelector('#output');

const streamAnswer = async (prompt) => {
  const response = await fetch('/api/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ prompt })
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);

  const reader = response.textStream().getReader();
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    output.textContent += value; // Text erscheint, sobald er ankommt
  }
};

streamAnswer('Erkläre Streams in einem Satz.');

Feature-Erkennung mit Fallback über TextDecoderStream

const getTextStream = (response) =>
  typeof response.textStream === 'function'
    ? response.textStream()
    : response.body.pipeThrough(new TextDecoderStream());

const response = await fetch('/logs/server.log');
const reader = getTextStream(response).getReader();
let chars = 0;
for (let r = await reader.read(); !r.done; r = await reader.read()) {
  chars += r.value.length;
}
console.log(`Gelesene Zeichen: ${chars}`);
Gelesene Zeichen: 48213

NDJSON zeilenweise verarbeiten (asynchrone Iteration)

const response = await fetch('/api/events.ndjson');
let buffer = '';

for await (const chunk of response.textStream()) {
  buffer += chunk;
  const lines = buffer.split('\n');
  buffer = lines.pop(); // unvollständige letzte Zeile aufheben
  for (const line of lines) {
    if (line.trim()) console.log(JSON.parse(line));
  }
}
if (buffer.trim()) console.log(JSON.parse(buffer));
{ id: 1, type: 'start' } { id: 2, type: 'progress' } { id: 3, type: 'done' }

// Wichtig · Fallstricke

  • Kompatibilität: textStream() ist sehr neu bzw. experimentell und noch nicht in allen Browsern verfügbar. Deshalb immer per typeof response.textStream === 'function' prüfen und auf response.body.pipeThrough(new TextDecoderStream()) zurückfallen.
  • Nur UTF-8: Die Dekodierung geschieht immer als UTF-8, unabhängig vom charset im Content-Type. Für andere Kodierungen new TextDecoderStream('iso-8859-1') o. Ä. verwenden.
  • Chunk-Grenzen sind beliebig: Ein Abschnitt kann mitten in einer Zeile oder in einem JSON-Objekt enden. Zeilen- oder Nachrichtengrenzen muss man selbst puffern (siehe NDJSON-Beispiel).
  • Einmalig lesbar: Nach dem Aufruf ist der Body verbraucht. Wer ihn zusätzlich anders auswerten will, erstellt vorher mit response.clone() eine Kopie.
  • Asynchrone Iteration (for await … of) über ReadableStream wird nicht überall unterstützt. Der getReader()-Ansatz funktioniert dagegen überall.
  • Abbrechen lässt sich der Stream per reader.cancel() oder über ein AbortSignal an fetch().