Suggest an editImprove this articleRefine the answer for “Streams: ReadableStream and WritableStream”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**A stream is a sequence of data that arrives gradually, in chunks, rather than all at once.** A `ReadableStream` is a data source (for example `response.body` after `fetch`), a `WritableStream` is a sink, and a `TransformStream` converts data on the fly. Streams are chained with `pipeThrough()` and `pipeTo()`, so you can process gigabytes without loading everything into memory. ```javascript const response = await fetch('https://example.com/large-file.txt'); const reader = response.body.getReader(); const { done, value } = await reader.read(); // value is a Uint8Array ``` **Key point:** streams give you chunked processing, low memory use and composition through pipes instead of waiting for a full download.Shown above the full answer for quick recall.Answer (EN)Image**A stream is a sequence of data that arrives gradually rather than all at once.** A plain `fetch().text()` waits until everything has arrived and only then returns a result, while a stream (`response.body`) lets you process the data piece by piece, as it comes in, chunk by chunk. ## Theory ### TL;DR - A stream is data arriving in chunks rather than in one block. - `ReadableStream` is a source, `WritableStream` is a sink, `TransformStream` is a converter. - `response.body` after `fetch()` is a ready-made `ReadableStream`. - `reader.read()` returns a promise with `{ done, value }`, where `value` is a `Uint8Array`. - Streams are chained with `pipeThrough()` and `pipeTo()`. - The main benefit: processing large volumes without loading everything into memory. ### Quick example ```javascript const response = await fetch('https://example.com/large-file.txt'); // response.body is a ReadableStream const reader = response.body.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; console.log('chunk size:', value.length); } ``` What happens: 1. `fetch` returns a `Response` whose `.body` is a **ReadableStream**. 2. The `.getReader()` method creates a reader that hands data back in chunks. 3. `reader.read()` returns a promise with `{ done, value }`: - `value` is a piece of data (type `Uint8Array`); - `done: true` means the stream is finished. This is how you download gigabytes without holding the whole file in memory. ### Stream types in JavaScript | Type | Purpose | | --- | --- | | **ReadableStream** | data source (reading) | | **WritableStream** | data sink (writing) | | **TransformStream** | intermediate processing (read plus write) | | **DuplexStream** | two-way, a socket for example | ### Writing data to a WritableStream ```javascript const stream = new WritableStream({ write(chunk) { console.log('writing chunk:', chunk); }, close() { console.log('stream finished'); }, }); const writer = stream.getWriter(); await writer.write('Hello'); await writer.write('World'); await writer.close(); ``` Explanation: - `WritableStream` accepts data in pieces through `write(chunk)`; - `close()` is called when the stream is finished; - it is handy for sending data, writing files, logging and so on. ### TransformStream and converting on the fly ```javascript const upperCaseStream = new TransformStream({ transform(chunk, controller) { controller.enqueue(chunk.toUpperCase()); }, }); const writer = upperCaseStream.writable.getWriter(); const reader = upperCaseStream.readable.getReader(); await writer.write('hello '); await writer.write('world!'); await writer.close(); while (true) { const { done, value } = await reader.read(); if (done) break; console.log(value); // "HELLO " "WORLD!" } ``` A `TransformStream` joins an input stream (`writable`) and an output stream (`readable`) and lets you convert data on the fly. ### Piping streams with pipeTo and pipeThrough Streams can be connected to each other like water pipes: ```javascript await readableStream .pipeThrough(transformStream) .pipeTo(writableStream); ``` In other words: - `pipeThrough` applies a transformation and returns a new readable; - `pipeTo` sends the result into a final sink. A practical example with `fetch()` and `TextDecoderStream`: ```javascript const response = await fetch('https://example.com/data.txt'); const textStream = response.body .pipeThrough(new TextDecoderStream()) // decode bytes into text .pipeThrough( new TransformStream({ transform(chunk, controller) { controller.enqueue(chunk.toUpperCase()); // transform the text }, }) ); const reader = textStream.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; console.log(value); } ``` Every piece of the file is first turned into text, then uppercased, and processed immediately, without waiting for the download to finish. ### Streams in Node.js and why they matter Node.js has its own implementation (`fs.createReadStream`, `stream.Writable` and others), but the concept is the same: ```javascript import fs from 'fs'; const readStream = fs.createReadStream('file.txt', { encoding: 'utf-8' }); readStream.on('data', chunk => console.log('Chunk:', chunk)); readStream.on('end', () => console.log('Done')); ``` Node.js streams are built on `EventEmitter`, while browser streams are built on promises and controllers. Modern Node.js also ships Web Streams through `node:stream/web`, so the code can move between environments. | Advantage | Description | | --- | --- | | **Performance** | No need to wait for a whole file to download | | **Low memory use** | Processing happens on the fly, in chunks | | **Composition** | Streams can be joined into chains | | **Two-way communication** | Used by WebSocket, `fetch()`, `Response`, `Body` | | **Control** | Reading can be paused and resumed (backpressure) | > A simple analogy: streams are a conveyor belt. > The data does not arrive whole, it travels along the belt in chunks, > and at every stage (Readable, Transform, Writable) you can process, filter and store it. | Type | Purpose | Example use | | --- | --- | --- | | **ReadableStream** | Data source | `fetch().body`, `file.stream()` | | **WritableStream** | Data sink | Writing to a file, a log, the network | | **TransformStream** | Converter | Compression, encryption, filtering | | **pipeTo / pipeThrough** | Connecting streams | `readable.pipeThrough(transform).pipeTo(writable)` | ### Common mistakes - **Reading `response.body` after `response.text()`.** A response body can only be consumed once: after `text()`, `json()` or `blob()` the stream is already locked. If you need both, use `response.clone()`. - **Forgetting to release the reader.** While `getReader()` is held, the stream stays locked. Call `reader.releaseLock()` or read the stream to the end. - **Ignoring backpressure.** Writing into a `WritableStream` without `await writer.write(...)` lets the queue grow, and the memory advantage of streaming disappears. - **Assuming `value` is a string.** `response.body` yields `Uint8Array`; to get text you need `TextDecoderStream` or `TextDecoder`. - **Not handling errors in the chain.** `pipeTo()` returns a promise that can reject; without a `catch`, a network failure goes unnoticed. - **Confusing Node.js streams with Web Streams.** They have different APIs: the first works through `data` and `end` events, the second through promises and controllers.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.