32

I have this very confusing snippet of code using es6 async await syntax. What I would expect to happen is that the process hangs on the await line forever, since the resolve function is never called. However, what actually happens is that "start" is outputted and then the process exits with no more output.

const simple = async () => {
  console.log('start')
  await new Promise(resolve => {})
  console.log('done.')
}
simple()

this code below however, will print "start", wait 1 second, and the print "done."

const simple = async () => {
  console.log('start')
  await new Promise(resolve => setTimeout(resolve, 1000))
  console.log('done.')
}
simple()

My closest guess to what this means (without any evidence) is that while node is waiting on a promise, it keeps track of the active things happening in your code, when there is nothing else happening, it simply exits. Can someone explain why the code exits here?

running node v8.7.0

andykais
  • 996
  • 2
  • 10
  • 27
  • There's one more thing I find worth mentioning so that it's easier to understand what's happening here. Since it's an `async`/`await` function, it differs from regular functions, which always run to completion. If you rewrite `simple` to not use `async`/`await`, the code that goes after the `await` statement will have to be placed inside a `.then()` callback. Since you don't call `resolve()`, that code will never execute and thus the console will not print "done". The body of the Promise is, however, executed immediately, and since it's empty, there is nothing left for the program to execute! – Avius Jun 27 '19 at 15:59
  • 1
    A good point! Thinking about this without `async`/`await` syntax, it is much more obvious why the code stops executing – andykais Jul 03 '19 at 17:14
  • A good explanation can be found here: https://stackoverflow.com/a/36735167/890357 – marciowb Jul 20 '20 at 22:02

2 Answers2

13

My closest guess … it keeps track of the active things happening in your code, when there is nothing else happening, it simply exits.

This is essentially correct. Node keeps a reference count of things like timers and network requests. When you make a network, or other async request, set a timer, etc. Node adds on to this ref count. When the times/request resolve Node subtracts from the count.

This count is how Node decides whether to exit at the end of the event loop. When you get to the end of the event loop Node looks at this count and if it's zero exits. Simply making a promise, doesn't add to the ref count because it's not an async request.

There's a good talk about some of this by [Node core developer] Bert Belder that's helpful: https://www.youtube.com/watch?v=PNa9OMajw9w

Mark
  • 90,562
  • 7
  • 108
  • 148
  • thank you that talk is very insightful! The point about `ref++` and `ref--` keeping track of each thing and Promises not adding a count paints a very clear picture of why this program exits – andykais Oct 27 '17 at 04:12
  • 2
    Why doesn't the third line `done` keep the process alive? There is more JavaScript to execute! I understand the empty promise not adding callbacks to the queue, but why is the rest of the script ignored? – pinhead Feb 27 '19 at 20:49
  • 2
    @pinhead Async functions turn the code after await to another function under the hood that only gets ran when the promise given to await resolves. If the promise won't ever resolve, what's the point in waiting for some code that will never run? – PoolloverNathan Mar 09 '22 at 21:52
  • Is there a way to create your own reference into the event loop? – CMCDragonkai Aug 06 '22 at 05:55
11

Right, you have a misunderstanding of what's happening.

Like you say, code will never continue after that await call for the reason you mention, but that doesn't matter.

When you call simple(), that method itself returns a promise - and you're not waiting on that promise. Your execution continues after calling simple() and instantly and hits the end of your program.

To go into more detail, nodejs will exit when there are no more callbacks to process. When you return your broken promise, you have not created a callback in the nodejs queue (like you would if you did a http request for instance). If you do something to keep nodejs alive, you'll see that done never gets executed.

const simple = async () => {
  console.log('start')
  await new Promise(resolve => {})
  console.log('done.')
}
var promise = simple();

// keep the application alive forever to see what happens
function wait () {
    setTimeout(wait, 1000);
};
wait();

If there is a pending callback (created by setTimeout, or from loading a resource, or waiting for an http response) then nodejs will not exit. However, in your first example, you don't create any callbacks - you just return a broken promise. Nodejs doesn't see anything "pending" there, so it exits.

Essentially at the end of your simple call, you have no more code to execute, and nothing pending (no waiting callbacks) so nodejs safely knows there is nothing else to be done.

caesay
  • 16,932
  • 15
  • 95
  • 160
  • youre right that done will never get executed, but this code can wait upon a promise that is doing something in there, if you replace the `await` line with `await new Promise(resolve => setTimeout(resolve, 1000))`, the code will output "done" afterwards without `simple()` needing any handlers. I have updated my question to show this – andykais Oct 27 '17 at 03:19
  • @andykais yes! exactly. that's exactly what i said. the `setTimeout` method creates a **callback** which nodejs is looking for. nodejs will not exit if there is any pending callbacks. your first example does **not** create any pending callbacks, it simply returns a promise. – caesay Oct 27 '17 at 03:46