Streams process data chunk by chunk. response.body is a ReadableStream of bytes; a WritableStream is a destination; a TransformStream sits between. pipeThrough() chains transforms, pipeTo() ends in a sink, and tee() splits a stream. Pipes apply backpressure: a full sink queue stops the source. Streams are Baseline Widely available (since December 2024), as is CompressionStream for gzip and deflate.
<pre id="out" style="font: 15px monospace"></pre>
<script type="module">
const text = 'Streams deliver data in chunks. '.repeat(500);
const zipped = await new Response(
new Blob([text]).stream().pipeThrough(new CompressionStream('gzip'))).bytes();
let chunks = 0, chars = 0;
await new Blob([zipped]).stream()
.pipeThrough(new DecompressionStream('gzip'))
.pipeThrough(new TextDecoderStream()) // bytes -> strings
.pipeThrough(new TransformStream({ transform: (s, ctl) => ctl.enqueue(s.toUpperCase()) }))
.pipeTo(new WritableStream({ write(chunk) { chunks++; chars += chunk.length; } }));
out.textContent = `${text.length} chars -> ${zipped.length} gzip bytes\n`
+ `unzipped: ${chars} chars in ${chunks} chunk(s)`;
</script>
To show a download progressively, loop on reader.read() until done; for await over a stream is not yet Baseline.