Signatur
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
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}`);
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));
// Wichtig · Fallstricke
- Kompatibilität:
textStream()ist sehr neu bzw. experimentell und noch nicht in allen Browsern verfügbar. Deshalb immer pertypeof response.textStream === 'function'prüfen und aufresponse.body.pipeThrough(new TextDecoderStream())zurückfallen. - Nur UTF-8: Die Dekodierung geschieht immer als UTF-8, unabhängig vom
charsetimContent-Type. Für andere Kodierungennew 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) überReadableStreamwird nicht überall unterstützt. DergetReader()-Ansatz funktioniert dagegen überall. - Abbrechen lässt sich der Stream per
reader.cancel()oder über einAbortSignalanfetch().