Understanding JavaScript Timers

You’re probably familiar with Javascript’s built-in timer functions: setTimeout and setInterval. At face value their behaviour appears straightforward and predictable. However JavaScript’s single threaded nature can cloak these seemingly innocent little features with mystery and intrigue, not to mention hidden powers which can be harnessed for the greater good…

The basics
(based on https://developer.mozilla.org)

//Executes a code snippet or a function after specified delay
var timeoutID = window.setTimeout(func, delay);

//Clears the delay set by window.setTimeout
window.clearTimeout(timeoutID)

//Calls a function repeatedly, with a fixed time delay between each call to that function.
intervalID = window.setInterval(func, delay);

//Cancels repeated action which was set up using setInterval().
window.clearInterval(intervalID)

Note 1: In place of a function you could use a string representation of the code to be evaluated, but if you do that you’ll have the eval police all over you.

Note 2: For reasons unknown to me, ECMA and W3C have consistently ignored JavaScript timers. Finally the WHATWG group draft (aka HTML 5) has come up trumps. The most interesting requirement is for optional function parameter arguments in setTimeout and setInterval – long supported by Mozilla and the webkit broswers but not by IE

//use of optional functional parameter argument (not supported in IE8)
setTimeout(alert, 1000, "WHATWG want me to do this");

What really happens

Imagine yourself as a function in a single threaded environment. Its not unlike driving along a single lane highway towards a tunnel (the function executer) with a bunch of other functions lined up behind you. There are no other threads so there are no overtaking lanes. Just before you reach the invocation tunnel a traffic cop pulls you over and informs you that you are the subject of a setTimeout call. He asks you to wait for the specified time period. Other functions will overtake and get invoked first. After you have sat out the required delay the traffic cop waves you on. But you can’t always just pull back into the road to enter the tunnel – you still have to let other functions go ahead until there is a break in traffic. This is why even a 1 ms timeout will push a function to the back of the invocation queue. The 1 ms delay is by no means certain, in fact 1ms turns out to be the minimum time you will wait.

console.log(1);
setTimeout(function() {console.log(2)},1);
console.log(3);
console.log(4);
console.log(5);
//console logs 1,3,4,5,2

Why is this useful? By enforcing a timeout (however small) you remove the function from the current execution queue and hold it back until the browser is not busy.

The most obvious benefit is that you can make long running functions (on which no other immediate function is dependent) defer execution until more urgent routines have completed. Consider a module that performs some animation based on user input. We want to log the latest user data but that aspect is less time-critical:

var processUserInput = function(event) {
    processState(getNewState(event));
    //user visible tasks, get priority....
    animateToNewState();
    solicitUserInput();
}

var processState = function(newState) {
    updateState(newState);
    //logging is low priority, make it wait
    setTimeout(logCurrentState,1);
}

In this way we enforced a sort of queuing priority system, with lower priority tasks getting wrapped by setTimeout.

In a similar vain, you can ensure that an alert message or other modal messaging device does not halt the immediate execution flow.

var a = 0
setTimeout(function() {alert("a = " + a)},1);
a += 4; //alerts "a = 4"

Note the interesting and potentially useful side effect. The alert message waits for the value of a to be updated before displaying it. In this simple example you could equally well just move the alert to bottom and achieve the same effect, but in cases where the variable (or a DOM element) operators are not rigidly defined (for example they might be event driven) this technique might be worth exploring further.

(Incidentally, I’ve read several articles in which the author claims setTimeout will allow your functions to run asynchronously. This is untrue. The firing of the setTimeout callback is asynchronous, the function itself will be invoked in line and after the current invocation queue).

Its also worth noting that the timing logistics of setInterval differ from setTimeout in the following way. In the case of setTimeout, the clock only starts ticking once the setTimeout function is invoked. Therefore nested setTimeouts will guarantee a minimum interval between callbacks.

You can try this out for yourself:

var recurse = function() {
    console.log(+new Date);
    if (recurse.counter++ == 4) {
        recurse = function() {}; //quit
    }
    setTimeout(recurse,10);
    var i = 0;
    //waste some time
    while (i++ < 10000) {
    }
}

recurse.counter = 1;

recurse();

setInterval, on the other hand, will fire each callback at precisely the originally designated time, regardless of additional delays that may have been experienced by previous callbacks waiting to re-enter the invocation queue. (I don’t think its possible to write code to prove this, since the logging function can only be invoked from the function that setInterval wraps, and even though the callback triggering is precisely timed, invocation of the function itself may be delayed for the reasons described above).

You can think of it like a train line. For nested setTimeouts with delays set to 1000ms, trains will run at a minimum of 1000ms apart. However when using setInterval every train will leave the station at precise 1000ms intervals even if a train is delayed ahead. Thus trains (or functions) can end up running back to back.

For further reading on this and other timer issues see: John Resig –  How JavaScript Timers Work
For some wonderful examples of how to fully leverage JavaScrip timers, check out Oliver Steele’s Sequentially project

11 thoughts on “Understanding JavaScript Timers

  1. Hey Angus,

    It’s also worth noting that while you can `setTimeout` with a 0ms delay in most browsers and get the actual minimum that is around 10-20ms, MSIE (of course) doesn’t play nice with `setInterval` and a 0ms delay. It will only execute the function once! (First read this in John Resig’s upcoming book).

    Nick

  2. Nice overview.

    In addition, it’s good to remember that SpiderMonkey passes an expired time delay as a first argument to the callback function (an old hack which still in current Firefox):

    setTimeout(function  () {
      alert(arguments[0]); // ah?
    }, 0);

    So, if you program for Firefox and wanna to pass some arguments as an additional parameters of setTimeout/setInterval, consider this feature. Generally, it tries to pass this time-delay as the last free argument (i.e. if some argument isn’t passed — its placed will be taken by this implicit time-delay argument):

    var count = 0;
    
    function foo(bar, baz) {
      if (count > 1) {
        return;
      }
      count++;
      alert(bar); // first call - 100, second - 100 
      alert(baz); // first - ?, second - 200
      setTimeout(foo, 0, 100, 200);
    }
    
    setTimeout(foo, 0, 100);

    Dmitry.

    1. Dmitry – I did not know about this – thanks. However the results are surprising. What is the delay relative too?

      For example if I do this (made timeout 100):

      setTimeout(function  () {
        alert(arguments[0]); /
      }, 100);

      the alert is generally a negative number

      1. In general, they try to put there exactly expired time delay, but sometimes, when there is no such delay it can be 0 (which seems correct) or some negative number (which, yeah, seems odd). You can the case that this is a milliseconds time delay by inserting some blocking code, e.g. alert(…) right after setTimeout code, then release the alert message and see the delay (how much time the alert window was shown):

        setTimeout(function  () {
          alert(arguments[0]);
        }, 100);
        
        alert(1); // show it for a while..
        
        // after release, you'll see the expired time (more-less exact)

        Notice also, that this argument tries to get a place of the first empty argument. So if will be three arguments and you pass just one, then it will be the second one:

        setTimeout(function  (a, b, c) {
          alert([a, b, c]); // 10, ? (time), undefined
        }, 0, 10);

        In addition, some issues in the bug-tracker:

        https://bugzilla.mozilla.org/show_bug.cgi?id=10637
        https://bugzilla.mozilla.org/show_bug.cgi?id=263945

        Dmitry.

  3. Good post.
    But is your following code snippet not a recipe for a stack overflow?

    var updateState = function(newState) {
    updateState(newState);
    //logging is low priority, make it wait
    setTimeout(logCurrentState,1);
    }

Leave a reply to Nick Fitzgerald Cancel reply