Skip to content

Readable Streams

A Readable is a stream from which data can be consumed. Node’s built-in examples include fs.createReadStream (reading a file), process.stdin (terminal input), and HTTP request bodies. You can also create your own readable sources with Readable.from or by extending Readable.

Readable streams operate in two modes:

  • Flowing mode — data events fire continuously as chunks arrive. You attach a data listener to enter this mode.
  • Paused mode — chunks accumulate until you explicitly call .read(). This is the default until you attach a listener or pipe.

The cleanest modern approach is async iteration with for await...of, which handles paused mode and backpressure automatically.

The classic event-based API predates async iteration. It remains important to understand because many Node APIs still emit these events:

const fs = require('node:fs');
const stream = fs.createReadStream('./file.txt', { encoding: 'utf8' });
stream.on('data', function(chunk) {
console.log('received chunk of length', chunk.length);
});
stream.on('end', function() {
console.log('no more data');
});
stream.on('error', function(err) {
console.error('stream error', err.message);
});
  • data — fires each time a chunk is available. The chunk is a Buffer by default, or a string if encoding is set.
  • end — fires once when there is no more data to consume.
  • error — fires if something goes wrong (file missing, permission denied, etc.).

Readable.from(iterable) converts any sync or async iterable into a Readable stream. It is useful for testing and for wrapping in-memory data sources:

const { Readable } = require('node:stream');
const stream = Readable.from(['chunk1', 'chunk2', 'chunk3']);

The iterable can be a plain array, a generator function, or an async generator — giving you a clean way to produce data lazily.

Since Node 10+, all Readable streams implement the async iterator protocol. This means you can consume them with for await...of inside an async function:

async function processStream(stream) {
for await (const chunk of stream) {
// handle each chunk
}
}

This approach is easier to read than event listeners, handles backpressure automatically, and surfaces errors as thrown exceptions that your try/catch can catch.

The demo below creates a Readable from an async generator, then consumes it with for await. Each generator yield represents one chunk arriving asynchronously.

Node.js

Needs the Node.js runtime — open in StackBlitz to run.

What event fires when a Readable stream has no more data to deliver?
What does Readable.from(iterable) do?
Which approach handles backpressure automatically when consuming a Readable?