5

When I execute the following code, I thought Node.js will wait till we call the resolve. Till then the myPromise will be in the <pending> state. Then how come node exits before it resolves?

The following code exits immediately!

const myPromise = new Promise(resolve => {
  // nothing doing with the resolve
});

myPromise
  .then(() => console.log('result'))
  .catch(error => console.log('error', error));

Update 1: I renamed JavaScript to Node.js to avoid confusion.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
Wajahath
  • 2,827
  • 2
  • 28
  • 37
  • 2
    You didn't `await` myPromise. – Unmitigated Mar 27 '23 at 15:27
  • 2
    Do you want your machine to hang itself until a promise that you forgot to resolve become resolved? – InSync Mar 27 '23 at 15:27
  • 2
    Are you interested in how they can know this? If no task is pending anywhere, and no event is being listened to, your Promise won't ever be resolved. – Kaiido Mar 27 '23 at 15:31
  • 2
    Basically, like @Kaiido says: Node exists _when there are no pending tasks_. `resolve => {}` in your example is a function that doesn't start any new tasks. If there were a `setTimeout` or `fetch`, there would be a pending task. If you were to call `resolve()` there would be one, too. But since there are no tasks to run, it's' free to exit. – RickN Mar 27 '23 at 15:33
  • @Unmitigated node doesn't kill the process as soon as the original script is executed, that would be pretty bad. – Kaiido Mar 27 '23 at 15:33
  • JavaScript will run `console.log('result')` once `resolve()` is called from inside of `new Promise(resolve => {})`. Learn more about [Promises on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) – Rojo Mar 27 '23 at 15:36
  • See [How the Event Loop Works in Node.js](https://heynode.com/tutorial/how-event-loop-works-nodejs/). In particular: "A Node.js process exits when there is no more pending work in the Event Loop, or when process.exit() is called manually. A program only runs for as long as there are tasks queued in the Event Loop, or present on the call stack." – jarmod Mar 27 '23 at 15:37
  • 1
    Minor side note: It's entirely possible for a promise to be *resolved* without being *settled* (fulfilled or rejected), or indeed without it *ever* being settled. More in my post on promise terminology [here](https://thenewtoys.dev/blog/2021/02/08/lets-talk-about-how-to-talk-about-promises/). – T.J. Crowder Mar 27 '23 at 15:42

3 Answers3

8

The following code exits immediately!

What keeps a Node.js process running is active in-progress work or the potential for it (pending timers, open sockets that may receive messages, etc.). So for instance, if your code had used readFile from fs/promises and the promise you were using came from that, Node.js wouldn't just terminate at the end of the code you've shown, because there's an operation in progress (reading the file). From the The Node.js Event Loop, Timers, and process.nextTick() guide:

Between each run of the event loop, Node.js checks if it is waiting for any asynchronous I/O or timers and shuts down cleanly if there are not any.

But in your example, there's nothing in progress, Node.js isn't waiting for anything, so it exits.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
2

Internally, NodeJS does use some referencing counting, eg. if you use a setTimeout, or setInterval, internally there will be some referencing counting. You can even control this to some extent, in nodeJS you can even call unref() on a the return value of setTimeout / setInterval to stop them from preventing node closing.

If you wanted to keep node open, no matter what the trick is then to have something were the reference count is greater than 0. One idea is using a MessageChannel from worker_threads.

eg.

const { MessageChannel } = require('worker_threads');
const port = new MessageChannel();
port.port1.ref();

//nodeJs will now stay open..
//and when your ready to close Node,  `port.port1.unref()
Keith
  • 22,005
  • 2
  • 27
  • 44
1

Promises are non-blocking. This means that when you invoke the asynchronous function, JavaScript does not wait for the promise to resolve. This is the whole purpose of promises: you can allow tasks to execute "in the background" (kind of) while JavaScript executes the rest of the script/function.

The side effect: If the browser/node runtime reaches the end of the script, the program will terminate, regardless of whether the Promise is resolved or not.

If you want to block JavaScript from terminating, you will have to block the main thread. Here is a solution I just came up with (if anybody has a better solution, leave it in the comments):

async function nonBlocking() { ... }

const interval = 100; // Blocks the main thread in 100 millisecond interval
const blockingInterval = setInterval(() => undefined, 100)

nonBlocking().then(value => {
    clearInterval(blockingInterval)
    // ... Rest of code here
}).catch(err => {
    clearInterval(blockingInterval)
    // ... Handle error here
})
Viraj Shah
  • 754
  • 5
  • 19
  • `you will have to block the main thread` Your terminology here is not really correct. But your code will keep Node alive because you are keeping an interval alive. – Keith Mar 27 '23 at 15:43
  • You're right. I meant to imply that the `interval` without any asynchronous code would technically be "blocking", which prevents the runtime for exiting – Viraj Shah Mar 27 '23 at 20:44