- Version: 11.2.0
- Platform: Windows 10 x64
- Subsystem: Streams
Description
After a readable handler has been added and removed from a stream, the stream no longer begins / resumes flowing when a data handler is added to the stream. This is a breaking change from 10.x and appears inconsistent with the (convoluted) intended behavior of streams. (More on this below.)
Related issues: #22209, #24366, #24281
Example
const stream = fs.createReadStream('foo.txt'); // some file that exists
const onData = () => { console.log('DATA'); };
const onReadable = () => {
console.log('READABLE');
stream.off('readable', onReadable);
stream.on('data', onData);
//stream.resume(); // <-- this causes data to be emitted
};
stream.on('readable', onReadable);
Expected Output:
The readable handler is called, and then the data handler is called.
Actual behavior:
The readable handler is called, but not the data handler.
Further Discussion
It is desirable for a stream to resume flowing when no readable handler is attached and a data handler is added. For example, one might generate a stream and check that it successfully opens (via the readable handler) before returning the stream to be consumed elsewhere (via the data handler). This worked at least up through 10.x.
With a certain reading, the current gap in behavior could be held as consistent with the "Three States" / "under the hood" explanation of stream modes, although that in itself may contradict the "Two Reading Modes" abstraction.
From streams documentation:
Two Reading Modes
All Readable streams begin in paused mode but can be switched to flowing mode in one of the following ways: (1) Adding a data event handler...
Adding a readable event handler automatically makes the stream to stop flowing, and the data to be consumed via readable.read(). If the readable event handler is removed, then the stream will start flowing again if there is a data event handler.
Three States
Calling readable.pause(), readable.unpipe(), or receiving backpressure will cause the readable.readableFlowing to be set as false, temporarily halting the flowing of events but not halting the generation of data. While in this state, attaching a listener for the 'data' event will not switch readable.readableFlowing to true.
Without having looked at code, I interpret the "Three States" description to indicate that there is no way to return from the readableFlowing == false state to the readableFlowing == null state. Hence, adding a readable handler destroys the auto-start behavior of the data handler.
This is sufficiently counter-intuitive that a slew of issues have been filed in the past few months, such as #24366 and #24281. In fact, a patch was even accepted in #22209 that ensures the auto-start behavior of a data handler IFF it was added before the readable handler.
Description
After a
readablehandler has been added and removed from a stream, the stream no longer begins / resumes flowing when adatahandler is added to the stream. This is a breaking change from10.xand appears inconsistent with the (convoluted) intended behavior of streams. (More on this below.)Related issues: #22209, #24366, #24281
Example
Expected Output:
The
readablehandler is called, and then thedatahandler is called.Actual behavior:
The
readablehandler is called, but not thedatahandler.Further Discussion
It is desirable for a stream to resume flowing when no
readablehandler is attached and adatahandler is added. For example, one might generate a stream and check that it successfully opens (via thereadablehandler) before returning the stream to be consumed elsewhere (via thedatahandler). This worked at least up through10.x.With a certain reading, the current gap in behavior could be held as consistent with the "Three States" / "under the hood" explanation of stream modes, although that in itself may contradict the "Two Reading Modes" abstraction.
From streams documentation:
Two Reading Modes
Three States
Without having looked at code, I interpret the "Three States" description to indicate that there is no way to return from the
readableFlowing == falsestate to thereadableFlowing == nullstate. Hence, adding areadablehandler destroys the auto-start behavior of thedatahandler.This is sufficiently counter-intuitive that a slew of issues have been filed in the past few months, such as #24366 and #24281. In fact, a patch was even accepted in #22209 that ensures the auto-start behavior of a
datahandler IFF it was added before thereadablehandler.