setTimeout waits too long in Node.js

2020-07-30 01:00发布

问题:

I think I have a misunderstanding of what setTimeout does exactly in JavaScript. I have this script:

function recursiveFibonacci(n) {
    if ( n === 0 ) {
        return 1;
    } else if ( n === 1 ) {
        return 1;
    } else {
        return recursiveFibonacci(n-1) + recursiveFibonacci(n-2);
    }
}

setTimeout( () => {console.log("one second");}, 1000);

console.log(recursiveFibonacci(42));

What I would expect to happen is that recursiveFibonacci starts chugging away on the 43rd value in the Fibonacci sequence. This takes about 4 seconds on my computer. So, after 1 second of work the evaluation would be interrupted and the console would log:

one second

and then about 3 seconds later log:

433494437

Instead what happens is that, after 4 seconds, the console logs:

433494437
one second

all at once. Why is this the case? How can I get setTimeout to work? Is it the case that the JavaScript interpreter is not actually interrupted by setTimeout but rather if it finishes its other jobs then it will wait until the given amount of time has passed before calling the given function?

Edit:

I found this tool very useful for understanding the relevant concepts:

Loupe

回答1:

Javascript in node.js is event driven and single threaded. Since your reverseFibonacci() function is synchronous, it does not allow anything else to run until it is done.

So, even though the timer fires and the timer callback is put into the internal nodejs event queue, the JS interpreter can't get to the next event in the event queue until your reverseFibonacci() function is done.

setTimeout() does not interrupt the currently running Javascript. Instead, it puts an event in the event queue and when the currently running Javascript is done, the JS interpreter will then pull the next event out of the event queue and run it.

So, the order of events in your scenario is this:

  1. Schedule a timer event for 1 second from now.
  2. Start running reverseFibonacci()
  3. Timer fires (internally in nodejs) 1 second later and inserts an event into the nodejs event queue.
  4. 4+ seconds later your reverseFibonacci(42) finishes running.
  5. Since the currently running JS has finished, the JS interpreter pulls the next event out of the event queue and processes it, resulting in your timer callback finally getting called.

Timers schedule with setTimeout() runs as soon as possible after their scheduled time, but if the Javascript interpreter is busy running other code, they don't interrupt that code. The timer system puts an event into the event queue and, when that other code is done, then the Javascript interpreter will pull the next event out of the event queue and run it (your timer callback will get called).



回答2:

It's because your function does synchronous job.

The nature of the node event loop is asynchronous. As far as you function finishes execution or you will execute something asynchronous - nextTick will be called. And most probably the first item will be you setTimout function.

Here's example with intended asynchronous chunk, and it works as you expect:

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

async function recursiveFibonacci(n) {
  await sleep(10);
  if ( n === 0 ) {
      return 1;
  } else if ( n === 1 ) {
      return 1;
  } else {
      return await recursiveFibonacci(n-1) + await recursiveFibonacci(n-2);
  }
}

setTimeout( () => {console.log("one second");}, 1000);

recursiveFibonacci(25).then(console.log);