Start · Sprachen · JavaScript · Referenz · Response.body

Response.body

Eigenschaft

Die schreibgeschützte Eigenschaft <code>body</code> liefert den Inhalt einer <code>Response</code> als <code>ReadableStream</code> oder <code>null</code>, wenn es keinen Inhalt gibt.

seit JavaScript Baseline: alle modernen Browser (Firefox 65+), Nod Kategorie: webapi

Signatur

Response.body

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

Typ
ReadableStream<Uint8Array> | null
Beschreibung
Ein 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');
Fortschritt: 12 % Fortschritt: 47 % ... Fortschritt: 100 % Fertig: 5242880 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));
Ereignis: { id: 1, typ: 'login' } Ereignis: { id: 2, typ: 'klick' }

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
null true false 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`);
Entpackt: 184320 Zeichen

// Wichtig · Fallstricke

  • Nur einmal lesbar: Nachdem der Stream mit getReader() gesperrt oder gelesen wurde, werfen json(), text() usw. einen TypeError. Wird der Inhalt mehrfach gebraucht, vorher response.clone() aufrufen oder den Stream mit body.tee() aufteilen.
  • null prüfen: Bei HEAD-Anfragen, Status 204/304 und inhaltslosen Antworten ist body gleich null. Ein direkter Aufruf von response.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 ohne Access-Control-Expose-Headers fehlt 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 (oder TextDecoder mit { stream: true }) verwenden und Zeilen puffern.
  • Abbrechen: Mit reader.cancel() oder einem AbortController-Signal im fetch() lässt sich der Download beenden. Nicht gelesene Streams können Verbindungen offenhalten.
  • Opake Antworten: Bei mode: 'no-cors' ist body gleich null.
  • Kompatibilität: In allen modernen Browsern verfügbar (Firefox erst ab Version 65), in Node.js ab 18 mit nativem fetch. Asynchrone Iteration mit for await über ReadableStream funktioniert in Safari erst seit Kurzem bzw. teilweise noch nicht. Im Zweifel getReader() verwenden.