1

I encountered a weird issue with setTimeout inside a promise in a child process.

These are my files:

index.js:

const {spawnSync} = require('child_process');
const {resolve} = require('path');

const timeoutPromiseModule = resolve(__dirname, '.', 'timeout-promise');
const {stdout} = spawnSync('node', [timeoutPromiseModule]);
console.log(stdout.toString());

timeout-promise.js:

Promise.race([
    Promise.resolve(),
    new Promise((resolve, reject) => {
      setTimeout(() => {reject('too long')}, 10000);
    })
])
.then(x=> console.log('resolved'))
.catch(e => console.log('rejected'));

When I run node index.js I expected the output to be print immediatly but what actually happens is that the output hangs until setTimeout's callback is called by the child process.

What's causing this and how can this be resolved?

I'm guessing it's something to do with the child process's event loop that prevents the child process from closing until the messages empty?

I uploaded the code to GitHub for your convenience:

https://github.com/alexkubica/promise-race-settimeout-blocking-inside-child-process

Alex Kubica
  • 67
  • 10
  • 1
    Why do you think `spawnSync()` will not wait for the timer? Just because the timer is inside of `Promise.race()` does not exempt the node process from still waiting for the timer to fire before exiting. The nodejs process sees a pending timer and will not exit until that timer has finished. You can `.unref()` that timer if you don't want the process to wait for it. – jfriend00 Sep 25 '20 at 14:55
  • Thanks, using unref() might be helpful for my usecase. – Alex Kubica Sep 25 '20 at 19:46

1 Answers1

2

The reason for this is that spawnSync will not return until the child process has fully closed as stated in the documentation:

The child_process.spawnSync() method is generally identical to child_process.spawn() with the exception that the function will not return until the child process has fully closed. [...]

Note that a node script will only exit when there are no more pending tasks in the eventloop's queue, which in this case happens after the timeout has resolved.

You can switch to spawn to see the immediatley resolved promise output:

const res = spawn('node', [timeoutPromiseModule]);

res.stdout.on('data', (data) => {
    console.log(`stdout: ${data}`);
});

res.on('close', (code) => {
    console.log(`child process exited with code ${code}`);
});
eol
  • 23,236
  • 5
  • 46
  • 64