Start · Sprachen · JavaScript · Referenz · CompressionStream.readable

CompressionStream.readable

Eigenschaft

Schreibgeschützte Eigenschaft, die den <code>ReadableStream</code> eines <code>CompressionStream</code> liefert, aus dem die komprimierten Daten als <code>Uint8Array</code>-Chunks gelesen werden.

seit JavaScript Baseline 2023: Chrome 80+, Edge 80+, Firefox 113+, Kategorie: webapi

Signatur

CompressionStream.readable

Beschreibung

Ein CompressionStream ist ein Transform-Stream und besteht aus zwei Seiten: Über writable schreibt man unkomprimierte Bytes hinein, und über readable kommen die komprimierten Bytes im gewählten Format ("gzip", "deflate" oder "deflate-raw") heraus. Die Eigenschaft readable gibt dabei immer dieselbe ReadableStream-Instanz zurück.

Meist greift man nicht direkt auf readable zu, sondern übergibt das ganze CompressionStream-Objekt an ReadableStream.prototype.pipeThrough(). Diese Methode verbindet die Quelle mit writable und liefert readable als Ergebnis zurück. Direkt mit readable arbeitet man, wenn man Daten manuell über einen Writer einspeist oder den Ausgabestrom selbst mit getReader(), for await…of oder pipeTo() weiterverarbeiten will.

Die Chunks, die aus readable kommen, sind immer Uint8Array-Objekte. Ihre Größe hängt von der Implementierung ab und entspricht nicht unbedingt der Größe der Eingabe-Chunks. Die letzten komprimierten Bytes, etwa die gzip-Prüfsumme und die Längenangabe, erscheinen erst, wenn die writable-Seite geschlossen wurde.

Rückgabewert

Typ
ReadableStream<Uint8Array>
Beschreibung
Der lesbare Ausgabestrom mit den komprimierten Daten als Uint8Array-Chunks.

Beispiele

Text mit pipeThrough() gzip-komprimieren

const compressText = async (text) => {
  const stream = new Blob([text]).stream()
    .pipeThrough(new CompressionStream('gzip')); // liefert cs.readable
  const buffer = await new Response(stream).arrayBuffer();
  return new Uint8Array(buffer);
};

const input = 'Hallo Welt! '.repeat(1000);
const gz = await compressText(input);
console.log(`Original: ${input.length} Bytes, komprimiert: ${gz.byteLength} Bytes`);
console.log(gz[0].toString(16), gz[1].toString(16)); // gzip-Magic-Bytes
Original: 12000 Bytes, komprimiert: ca. 60 Bytes 1f 8b

Manuell schreiben und readable per Reader auslesen

const cs = new CompressionStream('deflate');
const writer = cs.writable.getWriter();
const encoder = new TextEncoder();

// Schreiben läuft parallel zum Lesen, damit kein Rückstau blockiert
const writing = (async () => {
  await writer.write(encoder.encode('{"user":"anna",'));
  await writer.write(encoder.encode('"role":"admin"}'));
  await writer.close(); // erst jetzt werden die letzten Bytes ausgegeben
})();

const reader = cs.readable.getReader();
const chunks = [];
for (;;) {
  const { done, value } = await reader.read();
  if (done) break;
  console.log('Chunk:', value.constructor.name, value.byteLength);
  chunks.push(value);
}
await writing;
console.log('Gesamt:', chunks.reduce((n, c) => n + c.byteLength, 0), 'Bytes');
Chunk: Uint8Array … Gesamt: … Bytes

Komprimierten Upload per fetch senden

const uploadCompressed = async (file) => {
  const cs = new CompressionStream('gzip');
  file.stream().pipeTo(cs.writable); // Eingabeseite füllen

  const response = await fetch('/api/upload', {
    method: 'POST',
    headers: { 'Content-Encoding': 'gzip' },
    body: cs.readable, // Ausgabeseite als Request-Body streamen
    duplex: 'half'     // für Streaming-Bodies erforderlich
  });
  return response.ok;
};

// const ok = await uploadCompressed(document.querySelector('input[type=file]').files[0]);

Node.js: readable mit for await…of lesen

// Node.js 18+ – CompressionStream ist global verfügbar
const source = new Blob(['Protokollzeile\n'.repeat(500)]).stream();
const compressed = source.pipeThrough(new CompressionStream('deflate-raw'));

let total = 0;
for await (const chunk of compressed) {
  total += chunk.byteLength;
}
console.log('Komprimierte Größe:', total);
Komprimierte Größe: ca. 30

// Wichtig · Fallstricke

  • Schreibgeschützt: readable lässt sich nicht neu zuweisen. Pro CompressionStream gibt es genau einen Ausgabestrom, und der ist nach dem Lesen verbraucht. Für neue Daten legt man eine neue Instanz an.
  • Sperre: Nach getReader(), pipeTo() oder der Übergabe als fetch-Body ist der Strom gesperrt (locked === true). Ein zweiter Zugriff wirft einen TypeError. Mit tee() kann man den Strom verzweigen.
  • Rückstau (Backpressure): Wer mit writer.write() schreibt und jedes await abwartet, ohne gleichzeitig aus readable zu lesen, kann hängen bleiben. Lesen und Schreiben müssen deshalb parallel laufen.
  • Unvollständige Ausgabe: Solange die writable-Seite nicht geschlossen ist, wird readable nicht beendet, und die Prüfsumme bzw. der Abschlussblock fehlt. Das Ergebnis wäre dann kein gültiges gzip/deflate.
  • Fehler: Wird die Eingabe abgebrochen oder schreibt man etwas anderes als BufferSource-Chunks (z. B. Strings), gerät readable in den Fehlerzustand. Strings vorher mit TextEncoder umwandeln.
  • Kompatibilität: "deflate-raw" wird erst ab Chrome 103, Firefox 113 und Safari 16.4 unterstützt. Streaming-Request-Bodies bei fetch (duplex: 'half') funktionieren derzeit nur in Chromium-Browsern und brauchen HTTP/2.