简体   繁体   中英

Node.js: setTimeout callbacks for same delay called synchronously?

I have the following code to demonstrate the issue:

let count = 5;
while (count--) {
    setTimeout(() => {
        console.log('timeout');
        process.nextTick(() => {
            console.log('tick');
        });
    }, 0);
}

const largeNumber = 20000;
for (let i = 0; i < largeNumber; i += 1) {
    for (let j = 0; j < largeNumber; j += 1) {
        // do nothing here, just be sure all the setTimeout callbacks are in the queue when exiting sync code
    }
}

The output I expect is the following:

timeout
tick
timeout
tick
timeout
tick
timeout
tick

Because the event loop checks the timeouts queue, it founds the first setTimeout callback, runs it, and check the nextTick queue after. And for the further setTimeout callbacks it should do the same.

But I get the following output:

timeout
timeout
timeout
timeout
timeout
tick
tick
tick
tick
tick

Why?

setTimeout and nextTick will each put a function on a queue of functions to call later.

When the JavaScript event loop isn't busy doing something else, it will look at that queue of functions to see if any are due to be run.

When the first timed out function is run, it uses nextTick to put a function on the end of the queue (due to run as soon as possible).

However, the next function on the queue is the next function put there by setTimeout and it is already due , so it runs first (and so on).

This is due to Deduplication :

For the timers and check phases, there is a single transition between C to JavaScript for multiple immediates and timers. This deduplication is a form of optimization, which may produce some unexpected side effects.

Your code exhibits the "unexpected side effects" caused by the deduplication optimization.

In fact, the example in the doc is very similar to your sample code. They are using setImmediate instead of setTimeout , but the concept is the same:

When there are multiple timer events waiting in the check phase, Node processes all of them before processing the nextTickQueue .

So because all of the setTimeout calls use a timeout of 0 , the callbacks all end up in the queue at the same time and due to the deduplication optimization, Node processes all of them which causes 'timeout' to be printed all 5 times. Once all of the setTimeout callbacks run, Node processes the nextTickQueue which causes all five process.nextTick callbacks to run which causes 'tick' to be printed 5 times.


Note that if you introduce a tiny variable delay so the timer events don't end up in the queue during the same check phase you will avoid the deduplication optimization and will get the output you were expecting:

let count = 5;
let delay = 0;
while (count--) {
  setTimeout(() => {
    console.log('timeout');
    process.nextTick(() => {
      console.log('tick');
    });
  }, delay += 1);  // use a tiny variable delay
}

const largeNumber = 20000;
for (let i = 0; i < largeNumber; i += 1) {
  for (let j = 0; j < largeNumber; j += 1) {
    // do nothing here, just be sure all the setTimeout callbacks are in the queue when exiting sync code
  }
}

Output:

timeout
tick
timeout
tick
timeout
tick
timeout
tick

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM