Explain flowing vs paused mode for readable streams. How do you consume a stream?
Quick Answer
A Readable stream is in paused mode by default; you pull data with .read() or switch to flowing mode by attaching a 'data' listener or calling .pipe(), where chunks are pushed automatically. Modern code often consumes streams with for-await-of, which handles flow and backpressure cleanly.
Detailed Answer
Answer: A Readable stream operates in one of two modes governing how data moves.
Paused mode (default): you explicitly pull data:
readable.on('readable', () => {
let chunk;
while ((chunk = readable.read()) !== null) {
process(chunk);
}
});
Flowing mode: data is pushed to you as fast as it arrives. You enter it by:
- attaching a
'data'listener, - calling
.pipe(), or - calling
.resume().
readable.on('data', chunk => process(chunk)); // now flowing
Switching: adding a 'data' handler or pipe() → flowing; .pause() → paused; removing pipes/handlers can pause again.
Modern, preferred approach — async iteration:
async function readAll(readable) {
let total = 0;
for await (const chunk of readable) { // handles flow + backpressure
total += chunk.length;
}
return total;
}
for await...of is the cleanest way to consume a stream: it respects backpressure, propagates errors as exceptions (usable with try/catch), and reads until the stream ends.
Gotcha: In flowing mode, if you attach a 'data' listener but the consumer is slow and you don't manage backpressure, memory can grow. pipe/pipeline/for await avoid this; a bare 'data' loop does not.