While studying nodejs streams, I wanted to check what happens if the end
listener is added after a filestream has been consumed and closed.
I am puzzled by what happens: no error event, no exception thrown, but the promise is not awaited and the node process is closed with return value 0:
const fs = require("node:fs");
const { setTimeout } = require("node:timers/promises");
run()
.then(() => console.log("done"))
.catch(console.error);
async function run() {
const myStream = fs.createReadStream(__filename);
myStream.on("data", () => console.log("stream data"));
myStream.on("error", (error) => console.log("stream error", error));
myStream.on("close", () => console.log("stream close"));
console.log("data listener added");
await setTimeout(2000);
await new Promise((resolve) => {
console.log("add end listener");
myStream.on("end", () => {
console.log("stream end");
resolve();
});
console.log("end listener added");
});
console.log("run end");
}
Output (the process stops immediately after printing this, it does not hang):
$ node stream1.js
data listener added
stream data
stream close
add end listener
end listener added
I get that "stream end" is not reached, not pleasant but I can work with it by first checking for stream.isClosed.
As a consequence it seems logical that "run end" is not reached: I expect the run
function to wait for the promise forever.
But why the process does not hang?
Can some nodejs expert explain what is happening?
Did I make an error with my promise handling?
I am using node v18.12.1 on Linux.
When you add the end
listener to your stream, it has already ended, therefore the listener will never be invoked. resolve()
is never executed and the await new Promise(...)
never comes to an end. Therefore, run end
is not logged either. And the promise returned by run
never resolves so that done
is also not logged.
On the other hand, after end listener added
has been logged, the event loop of Node.js is empty: there is nothing more to expect, because the file has been read. In such a situation, the Node.js process exits.
Just attaching a listener to an event does not make Node.js wait. Perhaps the following analogy helps:
http.createServer().on("request", express());
registers a request handler but then exists immediately. By contrast, the .listen
method makes the HTTP server wait for incoming requests, even if they are ignored because no request handler is registered at all:
http.createServer().listen(8080);
never exits.
Also, pending promises do not make Node.js wait. Compare
new Promise(function(resolve, reject) {
setTimeout(resolve, 5000);
});
which exits after 5 seconds, to
new Promise(function(resolve, reject) {
});
which exists immediately. It is not the promise that makes Node.js wait, but the setTimeout
.
You made no error, only a wrong expectation perhaps.