Flow mode versus pull-based streaming

A Node stream can be in either non-flowing (pulling) or flowing (pushing) mode. When we attach a data event to a stream, it enters flowing mode, which means as long as there is data, the data event will be called.

myStream.on('data', handlerFunction)  

In the prior example snippet, if myStream was just created (and therefore a non-flowing stream by default), it would have been put into flowing mode via the act of attaching the data event.

If we want to stop data flowing through the stream, we can call the readable stream's pause method, and, when we want to start again, we can call the resume method.

myStream.pause() 
setTimeout(() => myStream.resume(), 1000)

In the previous example, if myStream was already in flowing mode, it would attempt to prevent incoming data when pause was called. A second later, myStream would notify incoming streams that it can receive data again.

See the There's more... section of the Decoupling I/O recipe for a full example and an in-depth explanation.

Flowing-mode can be problematic, since there are scenarios where the stream may be overwhelmed by incoming data-even if the stream is paused, incoming streams may disrespect the paused status.

An alternative way to extract data from a stream is to wait for a readable event and then continually call the stream's read method until it returns null (which is the stream terminator entity). In this way, we pull data from the stream, and can simply stop pulling if necessary.

In other words, we don't need to instruct the stream to pause and then resume; we can simply stop and start pulling as required.

Let's copy the self-read folder from the main recipe to self-read-pull:

$ cp -fr self-read self-read-pull 

Now we'll modify index.js to look like so:

const fs = require('fs') 
const rs = fs.createReadStream(__filename)

rs.on('readable', () => {
var data = rs.read()
while (data !== null) {
console.log('Read chunk:', data)
data = rs.read()
}
})

rs.on('end', () => {
console.log('No more data')
})

Now we're pulling data from the stream instead of it being pushed to an event handler. The readable event may trigger multiple times, as data becomes available, and once there's no data available the read method returns null.

The better way to extract data from a stream is to pipe (or as we'll see in later recipes, pump) the data into a stream which we've created. This way the problems with managing memory are managed internally. We'll cover using the pipe method in the next recipe.