Exactly when (in which NodeJS event loop phase) are a Readable stream’s “data”, “close” and “end” events emitted?

I thought I got a complete understanding of NodeJS event loop and its phases, but apparently not.

When are the handlers of the “data”, “close” and “end” event handlers executed? In which event loop phase or task queue?

The question is straightforward, but let me explain what I expect and what I don’t understand.

Let’s consider the following code (Node 19).

const stream = require('stream');

const readable = new stream.Readable({ read() {} });

// 1st PUSH
setImmediate(() => console.log('Immediate log 1'));
process.nextTick(() => console.log('Next tick log 1'));
queueMicrotask(() => console.log('Microtask log 1'));
readable.push(Buffer.from([1]));
console.log('Pushed data # 1');

// 2nd PUSH
setImmediate(() => console.log('Immediate log 2'));
process.nextTick(() => console.log('Next tick log 2'));
queueMicrotask(() => console.log('Microtask log 2'));
readable.push(Buffer.from([2]));
console.log('Pushed data # 2');

console.log('Attaching handlers....');
readable.on('data', data => console.log('Data received:', data))
    .on('end', () => console.log('End received'))
    .on('close', () => console.log('Close received'))
console.log('Handlers attached');

// 3RD PUSH
setImmediate(() => console.log('Immediate log 3'));
process.nextTick(() => console.log('Next tick log 3'));
queueMicrotask(() => console.log('Microtask log 3'));
readable.push(Buffer.from([3]));
console.log('Pushed data # 3');

// 4TH PUSH
setImmediate(() => console.log('Immediate log 4'));
process.nextTick(() => console.log('Next tick log 4'));
queueMicrotask(() => console.log('Microtask log 4'));
readable.push(Buffer.from([4]));
console.log('Pushed data # 4');

// NULL PUSH
setImmediate(() => console.log('Immediate log null'));
process.nextTick(() => console.log('Next tick log null'));
queueMicrotask(() => console.log('Microtask log null'));
readable.push(null);
console.log('Pushed null');

console.log('----END OF MAIN CODE----');

And here’s the output:

Pushed data # 1
Pushed data # 2
Attaching handlers....
Handlers attached
Pushed data # 3
Pushed data # 4
Pushed null
----END OF MAIN CODE----
Next tick log 1
Next tick log 2
Data received: <Buffer 01>
Data received: <Buffer 02>
Data received: <Buffer 03>
Data received: <Buffer 04>
Next tick log 3
Next tick log 4
Next tick log null
End received
Close received
Microtask log 1
Microtask log 2
Microtask log 3
Microtask log 4
Microtask log null
Immediate log 1
Immediate log 2
Immediate log 3
Immediate log 4
Immediate log null

I don’t understand how can be the 3rd and 4th data event handlers executed before my 3rd and 4th nextTick handlers event though I push data after I add my handlers to the nextTick queue.

For me, it’s like not the .push() but the .on('data') call somehow inserts a “listener-like” stuff into the nextTick queue, and it stays there in that place for all future data events. So anything I push to the nextTick queue after attaching the data event handler will be executed after the event handler, as you can see. It doesn’t make sense to me; I’d expect the .push() call to push the data event handler to the end of the nextTick queue.

What do I misunderstand here?

The “end” and “close” event handlers seem to make sense to me, since – as I can see – the .push(null) call pushes the handler to the end of the nextTick queue. However, initially I thought that – at least the “close” handler – is executed in the separate macrotask queue of close handlers, which comes after the immediate queue, but seemingly it’s not the case, am I right?