Description
- 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 viareadable.read()
. If thereadable
event handler is removed, then the stream will start flowing again if there is adata
event handler.
Three States
Calling
readable.pause()
,readable.unpipe()
, or receiving backpressure will cause thereadable.readableFlowing
to be set asfalse
, 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 switchreadable.readableFlowing
totrue
.
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.