setInterval weird behavior in nodejs

2019-02-19 12:26发布

问题:

I want to run a function once every second, and the function itself takes 3 seconds to execute. the results are that each interval is executed at a difference of <function execution time>*2+<setInterval delay>

I wrote the following sample code:

var seconds = 3;

setInterval(
    function(){
            console.info(new Date().toString());
            var waitTill = new Date(new Date().getTime() + seconds * 1000);
            while(waitTill > new Date()){}
    },1000
);

and each iteration is as I stated in the formula:

Wed Jul 13 2016 09:49:07 GMT+0300 (IDT)
Wed Jul 13 2016 09:49:14 GMT+0300 (IDT)
Wed Jul 13 2016 09:49:21 GMT+0300 (IDT)
Wed Jul 13 2016 09:49:28 GMT+0300 (IDT)

the documentation doesn't state this behavior. the results that I thought that will be is that each iteration will execute after 1 second no matter how much time the execution of the interval function takes.

what's going on?

any information regarding the issue would be greatly appreciated.

thanks!

using Nodejs 6.3.0

update

tried this code on the browser... google chrome... here the interval executes every 3 seconds, which is still weird.

update

thanks for all of your comments, one last thing that's not clear. why in NodeJS, when I have setInterval() set to 1 second, and the function execution takes 3 seconds, why the next execution is 7 seconds instead of 4 seconds or even 3 seconds. that seems really strange behavior for me. is that an acceptable behavior ?

回答1:

the documentation doesn't state this behavior

NodeJS's documentation for setInterval characteristically states virtually nothing about its behavior, other than that it will repeat the task.

the results that I thought that will be is that each iteration will execute after 1 second no matter how much time the execution of the interval function takes

If you mean you could have overlapping executions, you can't in NodeJS; it runs your code on a single thread.

If you mean you expected each iteration to run a second after the last completed, that's not how setInterval traditionally works. setInterval has traditionally had at least two different behaviors depending on which implementation you were using: Scheduling the next iteration at the beginning of the current one, or scheduling it at the end of the current one. And that's just on browsers. It's since been standardized for browsers, but NodeJS isn't a browser and isn't required to work the same way. (And in fact, it doesn't in another way as well: On browsers, setInterval is required to return a number; on NodeJS, it returns an object.) Remember that timers are not a feature of JavaScript, they're a feature of the host environment.

Instead, to get it to run again a second (roughly) after it finished previously, use setTimeout at the end of the function to schedule the next one to run a second later.

Re your edit:

why in NodeJS, when I have setInterval() set to 1 second, and the function execution takes 3 seconds, why the next execution is 7 seconds instead of 4 seconds or even 3 seconds. that seems really strange behavior for me. is that an acceptable behavior ?

Yes. It's strange and surprising (in my view), but NodeJS determines its own behavior for setInterval, so it's acceptable. In my experiments (below), it appears to measure how long the previous execution of your function took, then add that to the timer length so that it's lastExecutionLength + desiredInterval before it fires it again. That's markedly at variance with the spec for browsers, but again, NodeJS isn't a browser.

Here's my test script:

let counter = 0;
let timeAtEndOfLastExecution = 0;
let timer = null;

function log(msg) {
    console.log(Date.now() + ": " + msg);
}

function tick() {
    let start = Date.now();
    if (timeAtEndOfLastExecution) {
        log("tick (" + (Date.now() - timeAtEndOfLastExecution) + "ms)");
    } else {
        log("tick");
    }
    if (++counter == 10) {
        clearInterval(timer);
    } else {
        let wait = 200 + (Math.floor(8 * Math.random()) * 100);
        log("waiting " + wait + "ms");
        let stopWaiting = Date.now() + wait;
        while (Date.now() < stopWaiting) {
            // busy wait
        }
        log("exiting callback after " + (Date.now() - start) + "ms");
        timeAtEndOfLastExecution = Date.now();
    }
}
timer = setInterval(tick, 200);

And a sample run (with Node v6.2.2):

1468396730618: tick
1468396730619: waiting 400ms
1468396731020: exiting callback after 416ms
1468396731637: tick (617ms)
1468396731637: waiting 500ms
1468396732137: exiting callback after 500ms
1468396732837: tick (700ms)
1468396732837: waiting 900ms
1468396733737: exiting callback after 900ms
1468396734837: tick (1100ms)
1468396734837: waiting 300ms
1468396735137: exiting callback after 300ms
1468396735637: tick (500ms)
1468396735637: waiting 700ms
1468396736337: exiting callback after 700ms
1468396737237: tick (900ms)
1468396737237: waiting 800ms
1468396738037: exiting callback after 800ms
1468396739036: tick (999ms)
1468396739036: waiting 900ms
1468396739936: exiting callback after 900ms
1468396741036: tick (1100ms)
1468396741036: waiting 700ms
1468396741736: exiting callback after 700ms
1468396742636: tick (900ms)
1468396742636: waiting 200ms
1468396742836: exiting callback after 200ms
1468396743236: tick (400ms)

As we can see, it's consistently waiting the length of the previous iteration plus the interval I gave:

  • First callback took a total of 416ms; the next started 617ms after it returned
  • Second callback took 500ms; the next started 700ms after it returned
  • Third callback 900ms; the next started 1100ms after it returned