Node exits without error and doesn't await promise (Event callback)

This question is basically: how does node decide whether to exit the event loop or go around again?

Basically node keeps a reference count of scheduled async requests — setTimeouts, network requests, etc.. Each time one is scheduled, that count increases, and each time one is finished, the count decreases. If you arrive at the end of an event loop cycle and that reference count is zero node exits.

Simply creating a promise or event emitter does not increase the reference count — creating these objects isn't actually an async operation. For example, this promise's state will always be pending but the process exits right away:

const p = new Promise( resolve => {
    if(false) resolve()
})

p.then(console.log)

In the same vein this also exits after creating the emitter and registering a listener:

const ev = new EventEmitter()
ev.on("event", (e) => console.log("event:", e))

If you expect Node to wait on an event that is never scheduled, then you may be working under the idea that Node doesn't know whether there are future events possible, but it does because it keeps a count every time one is scheduled.

So consider this small alteration:

const ev = new EventEmitter()
ev.on("event", (e) => console.log("event:", e))

const timer = setTimeout(() => ev.emit("event", "fired!"), 1000)
// ref count is not zero, event loop will go again. 
// after timer fires ref count goes back to zero and node exits

As a side note, you can remove the reference to the timer with: timeout.unref(). This, unlike the previous example, will exit immediately:

const ev = new EventEmitter()
ev.on("event", (e) => console.log("event:", e))

const timer = setTimeout(() => ev.emit("event", "fired!"), 1000)
timer.unref()

There's a good talk about the event loop by Bert Belder here that clears up a lot of misconceptions: https://www.youtube.com/watch?v=PNa9OMajw9w