Signatur
Beschreibung
Die Eigenschaft Response.body gibt den Inhalt einer HTTP-Antwort als ReadableStream<Uint8Array> zurück. Anders als response.json() oder response.text(), die erst nach dem vollständigen Download auflösen, kann man mit dem Stream die Daten stückweise verarbeiten, sobald sie im Netzwerk ankommen.
Das lohnt sich vor allem bei großen Downloads mit Fortschrittsanzeige, bei zeilenweise gestreamten Formaten wie NDJSON oder Server-Sent-Antworten von KI-APIs, beim Weiterleiten von Daten über pipeThrough() (z. B. mit TextDecoderStream oder DecompressionStream) und immer dann, wenn der Speicherverbrauch niedrig bleiben soll.
Hat die Antwort keinen Inhalt, ist body gleich null. Das gilt z. B. für Antworten auf HEAD-Anfragen, für Status 204/304 oder für new Response(null). Ein Stream lässt sich nur einmal lesen. Danach ist response.bodyUsed gleich true, und weitere Leseversuche (auch json() oder text()) schlagen fehl.
Rückgabewert
ReadableStream, der die Rohbytes des Inhalts als Uint8Array-Stücke liefert, oder null, wenn die Antwort keinen Inhalt hat.Beispiele
Download mit Fortschrittsanzeige
const downloadMitFortschritt = async (url) => {
const response = await fetch(url);
if (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`);
const gesamt = Number(response.headers.get('Content-Length')) || 0;
const reader = response.body.getReader();
const teile = [];
let geladen = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
teile.push(value);
geladen += value.byteLength;
if (gesamt) console.log(`Fortschritt: ${Math.round((geladen / gesamt) * 100)} %`);
}
return new Blob(teile);
};
const blob = await downloadMitFortschritt('/dateien/video.mp4');
console.log('Fertig:', blob.size, 'Bytes');
NDJSON zeilenweise mit TextDecoderStream verarbeiten
const response = await fetch('/api/ereignisse.ndjson');
const reader = response.body
.pipeThrough(new TextDecoderStream())
.getReader();
let puffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
puffer += value;
const zeilen = puffer.split('\n');
puffer = zeilen.pop(); // unvollständige letzte Zeile aufheben
for (const zeile of zeilen) {
if (zeile.trim()) console.log('Ereignis:', JSON.parse(zeile));
}
}
if (puffer.trim()) console.log('Ereignis:', JSON.parse(puffer));
Auf fehlenden Inhalt prüfen
const leer = new Response(null, { status: 204 });
console.log(leer.body); // null
const mitText = new Response('Hallo Welt');
console.log(mitText.body instanceof ReadableStream); // true
console.log(mitText.bodyUsed); // false
await mitText.text();
console.log(mitText.bodyUsed); // true
Gzip-Datei im Stream entpacken (for await)
// for await über ReadableStream: Chrome 124+, Firefox 110+, Node.js 18+
const response = await fetch('/logs/app.log.gz');
const textStream = response.body
.pipeThrough(new DecompressionStream('gzip'))
.pipeThrough(new TextDecoderStream());
let zeichen = 0;
for await (const stueck of textStream) {
zeichen += stueck.length;
}
console.log(`Entpackt: ${zeichen} Zeichen`);
// Wichtig · Fallstricke
- Nur einmal lesbar: Nachdem der Stream mit
getReader()gesperrt oder gelesen wurde, werfenjson(),text()usw. einenTypeError. Wird der Inhalt mehrfach gebraucht, vorherresponse.clone()aufrufen oder den Stream mitbody.tee()aufteilen. nullprüfen: BeiHEAD-Anfragen, Status 204/304 und inhaltslosen Antworten istbodygleichnull. Ein direkter Aufruf vonresponse.body.getReader()löst dann einen Fehler aus.- Content-Length ist unzuverlässig: Bei komprimierten Antworten (
Content-Encoding: gzip) nennt der Header die komprimierte Größe, die Stream-Bytes sind aber schon entpackt. Bei Chunked Transfer und bei CORS ohneAccess-Control-Expose-Headersfehlt der Header oft ganz. - Stücke sind beliebig geschnitten: Stücke können mitten in einem UTF-8-Zeichen oder einer Zeile enden. Deshalb
TextDecoderStream(oderTextDecodermit{ stream: true }) verwenden und Zeilen puffern. - Abbrechen: Mit
reader.cancel()oder einemAbortController-Signal imfetch()lässt sich der Download beenden. Nicht gelesene Streams können Verbindungen offenhalten. - Opake Antworten: Bei
mode: 'no-cors'istbodygleichnull. - Kompatibilität: In allen modernen Browsern verfügbar (Firefox erst ab Version 65), in Node.js ab 18 mit nativem
fetch. Asynchrone Iteration mitfor awaitüberReadableStreamfunktioniert in Safari erst seit Kurzem bzw. teilweise noch nicht. Im ZweifelgetReader()verwenden.