Node: Nodejs does not wait for promise resolution - exits instead

Created on 2 Aug 2018  路  18Comments  路  Source: nodejs/node

  • Version: v10.6.0
  • Platform: Linux 4.15.0-24-generic #26~16.04.1-Ubuntu SMP Fri Jun 15 14:35:08 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
  • Subsystem:


The code bellow will end with output:

before
working

and then it ends. The expected output is:

before
working

and the node should not exit

function sleep(ms) {
    return new Promise((resolve) => { setTimeout(resolve, ms) })
}

class TestClass {
    async processData(data) {
        return new Promise((resolver, rejecter) => {
            console.log('working')
        })
    }
}

class RunnerClass {

    async start() {
        const instance = new TestClass()
        while (true) {
            console.log('Looping')
            if (true) {
                try {
                    console.log('before')
                    const processedData = await instance.processData('data')
                    console.log('after')
                } catch (error) {
                    console.log('errored')
                }
            }
            await sleep(1000)
        }
    }
}

async function run() {
    const rebalancer = new RunnerClass()
    await rebalancer.start()
    console.log('end')
}

run()

If the if statment is set to if (false) then the process is outputting:

Looping
Looping
...

and never ends...

promises

Most helpful comment

conceptionally speaking this is not good. IMO node should definitely wait until all promises resolved/rejected.

I guess it is this way, because it is very difficult to implement? As promises are a V8 internal thing and the node.js mother process doesn't know about them?

All 18 comments

If I am not mistaken, Node.js does not wait for ever-pending Promises:

'use strict';

(async function main() {
  await new Promise((resolve) => { console.log(42); });
})();
[exit]

If I am not mistaken, Node.js does not wait for ever-pending Promises

In other words, the mere existence of a Promise won't keep the process alive. You actually need to put something on the event loop (e.g. create a timer in the processData method).

By the way, even if the process stayed alive because of some other I/O, I think the expected output would be

before
working

because the promise Executor is invoked imediately.

The answers above are correct. Going to close this out.

@jiripospisil yes you are right, I edited issue. Of course that working is expected too.

@apapirovski but why the node end the process in the middle of nowhere? It has other code to execute after that await.

There is console.log('end') on the end. In this case I would expect that the process will throw en error or exits with some kind of error.

For example:

'use strict';

(async function main() {
  await new Promise((resolve) => { console.log(42); });
  console.log(`
    This will never be executed is this expected?
    Also node exits as everything is ok with exit code 0...
  `);
})();

I agree it's a bit weird when you encounter it for the first time (and I have certainly been bitten by this in the past) but the way to think about it is that when you await something, V8 jumps out of the current function (I believe it's implemented as syntax sugar on top of generators, or at least that's the high level idea). At this point Node.js doesn't care about the function anymore and checks whether there are some pending tasks on the event loop. If not, it exits. If you did this instead:

await new Promise((resolve) => { console.log(42); setTimeout(resolve, 2000) });

then Node.js would see there's a timer and stayed alive. When the timer goes off, Node.js invokes the callback and V8 jumps back to the place from which it jumped out off and starts executing the rest of the function.

Maybe we could have warning: "Process is exited, but you have pending promises, created on the file:xxx:yyy". But of course if will require some Promise manager and must be optional, just for debug.

This bit me pretty hard and has been extremely difficult to understand the behavior implications. I have a bundler which operates in a giant async Promise chain. Once a task completes it moves on to the next. I've added the ability for "loaders" to pre-process an AST and wanted to use a browserify plugin. I wrote the following code:

      transform: async ({ state, source }) => {
        return new Promise((resolve, reject) => {
          const stream = combynify(state.path, {});

          stream.on('data', console.log);

          stream.resume();
          stream.on('finish', function(d) {
            resolve(d);
          });

          stream.on('error', err => reject(err));
        });

This is injected into the Promise chain. Since I'm doing something wrong in this code, there is nothing on the event loop and immediately after this code executes, my bundler dies. This seems incredibly bad for an end user. How would they know what was wrong when they write such a loader? My bundler exits with no errors, exit code 0, and exits in the middle of this giant promise chain.

Should it be my bundlers responsibility to set a huge long timer before any loader, and once the loader is done, call clearTimeout?

This is how I've been able to "solve" the problem from my bundler's perspective:

    if (loaderChain.length) {
      const warningTimer = setTimeout(() => {
        console.log('Loader has not resolved within 5 seconds');
      }, 5000);
      const timer = setTimeout(() => {}, 999999);

      code = await handleTransform({ code, loaderChain });

      clearTimeout(timer);
      clearTimeout(warningTimer);
    }

@tbranyen as long as the stream you wrap is doing things it will work. if the stream absolutely stops but doesn't end or error, nothing will be keeping the process open anymore.

@devsnek I believe I covered that in my initial comment:

Since I'm doing something wrong in this code, there is nothing on the event loop and immediately after this code executes, my bundler dies.

The point here is that something was wrong, and given that everything in the chain was a Promise which either resolves, rejects, or hangs, this behavior feels out-of-place. If you were using my tool and there was no indication that the loader was successful or not (since it never resolves or rejects), and the tool simply dies with a non-error exit code, that's pretty bad for debugging. In a browser, Promises can just hang indefinitely since there is no "process" to close in the web page. In Node, I can see how this behavior is ambiguous since there is a distinction made in Node as to whether v8 or libuv scheduled events determine if the Node process is out of work or not.

conceptionally speaking this is not good. IMO node should definitely wait until all promises resolved/rejected.

I guess it is this way, because it is very difficult to implement? As promises are a V8 internal thing and the node.js mother process doesn't know about them?

@axkibe It's possible, see https://github.com/nodejs/node/issues/29355#issuecomment-525935873 for a PoC.

I just got bitten by this as well and it makes sense. I was able to solve it with a dummy timeout of 0 duration:

  await new Promise(resolve => {
    setTimeout(() => null, 0);
  })

This actually waits indefinitely or until I call the resolve function.

Edit: I was wrong, the dummy timeout with 0 duration does not keep it alive.

Also pretty sure I am experiencing this while running 80 or so child processes in a promise pool, 4 at a time in parallel. After getting to about 60 processes, or 2 minutes in, the script just exits.

I'm going to try putting a massive timeout before running the promise pool and see what happens.

setTimeout(() => {}, Number.MAX_SAFE_INTEGER);

Edit: the above didn't work, but I will share what did.

Node doesn't wait for this...

async () => {
    // some random awaits and stuff before here...

    return new Promise((resolve, reject) => {
        // fork() and more child process things in here
    });
}

However if you don't return the promise and instead resolve it with await, it will consistently stay alive:

const keepalive = await new Promise((resolve, reject) => {
    ...
}

return keepalive;

Very interesting engine quirk.

Just get into this issue. It is frustrated to debug without an message printed to console, telling the programmer that the progress is exited without an ever-pending Promise being resolved.

A warning message printed to the console would have saved my day.

Just get into this issue. It is frustrated to debug without an message printed to console, telling the programmer that the progress is exited without an ever-pending Promise being resolved.

A warning message printed to the console would have saved my day.

There is the 'beforeExit' event described in node docs
and called with process.on('beforeExit',...) -

  • Event: 'beforeExit'

    • The 'beforeExit' event is emitted when Node.js empties its event loop and has no additional work to schedule. Normally, the Node.js process will exit when there is no work scheduled, but a listener registered on the 'beforeExit' event can make asynchronous calls, and thereby cause the Node.js process to continue.

    • The listener callback function is invoked with the value of process.exitCode passed as the only argument.

    • The 'beforeExit' event is not emitted for conditions causing explicit termination, such as calling process.exit() or uncaught exceptions.

    • The 'beforeExit' should not be used as an alternative to the 'exit' event unless the intention is to schedule additional work.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

loretoparisi picture loretoparisi  路  3Comments

fanjunzhi picture fanjunzhi  路  3Comments

vsemozhetbyt picture vsemozhetbyt  路  3Comments

Icemic picture Icemic  路  3Comments

ksushilmaurya picture ksushilmaurya  路  3Comments