setInterval is (moderately) evil

bleroy05 JavaScript has two ways of delaying execution of code: setInterval and setTimeout. Both take a function or a string as the first parameter, and a number of milliseconds as the second parameter. The only difference is that the code provided to setInterval will run every n milliseconds whereas the code in setTimeout will run only once.

Before I explain why I think setInterval is evil, allow me to rant on a related subject for a paragraph: you should never pass a string into any of those functions and instead always pass a function reference (unless you really, really know what you’re doing). If you pass a string, it will have to be evaluated on the fly, and eval is quite evil itself (unless you really, really know what you’re doing). It might seem tempting to generate code this way to inject dynamic parameters, but there are better ways of doing that, using currying. In Microsoft Ajax, for example, we provide the handy Function.createCallback and Function.createDelegate for exactly that usage.

Anyways now let’s get back to the issue at hand: setInterval is evil. To better compare, let’s assume you have a reason to use setInterval (why else would you be reading this?). Here’s the code you might write:

<!DOCTYPE html>
<html>
<head>
    <title>setInterval</title>
    <script type="text/javascript">
        var interval = setInterval(appendDateToBody, 5000);

function
appendDateToBody() { document.body.appendChild( document.createTextNode(new Date() + " ")); } function stopInterval() { clearInterval(interval); } </script> </head> <body> <input type="button" value="Stop" onclick="stopInterval();" /> </body> </html>

Both APIs have a corresponding clear API that enables the developer to cancel the interval or timeout. To identify what interval or timeout you’re cancelling, you pass into the API a token that is whatever value the call to setInterval or setTimeout returned. And that’s a first reason why setInterval is (mildly) evil: if you lose that token, there is no way you can ever stop that code from running every five seconds until you navigate away from the page. But that is a minor inconvenience, it just means you need to carefully manage your own stuff, right? Well, actually if code that you don’t know or don’t control created that interval, you’re probably in trouble even if you kept your own house real nice and tidy…

But the real reason why I dislike setInterval is that it is quite hard to debug, in particular if you have more than one running simultaneously. For example, if you have two intervals, one running every 100 milliseconds, the other every five seconds, and if you want to debug the second one, the first one will constantly get triggered and will get in the way.

So what, you might say, is the alternative? Well, here is code that is quasi-equivalent to the code above, but that uses the much less evil setTimeout:

var interval = setTimeout(appendDateToBody, 5000);

function appendDateToBody() {
    document.body.appendChild(
        document.createTextNode(new Date() + " "));
    interval = setTimeout(appendDateToBody, 5000);
}

function stopInterval() {
    clearTimeout(interval);
}

Here, instead of taking a subscription for life, we’re just renewing the lease as we go. The big advantage of this code is that to stop the interval, you don’t need the token. Actually, I rarely even bother to keep hold of it. All you have to do is skip the line of code that renews the timeout for the next iteration. So no housekeeping to do, and if during a debugging session you need to get one of the interval functions out of the way, just drag your debugger’s current execution pointer to the end of the function, hit F5 and you’ll never see that function run ever again, boom, it’s out of the way and you can focus on your own debugging.

So to summarize, replacing setInterval with setTimeout is easy, pain-free and removes the inconveniences of setInterval. So while the setInterval evil is about at the level of not replacing the cap of the toothpaste, the non-evil alternative is so easy that I can’t see a reason not to forget about setInterval for good.

13 Comments

  • I agree completely. There's no compelling advantage of setInterval to offset the control that "recursive" setTimeout offers.

    Explicitly re-registering the setTimeout is more expressive too. There's no question what that code is doing.

  • Great heads-up on the currying capabilities of javascript. Here is a link from the wiki article with an example implementation: http://flesler.blogspot.com/2008/11/haskell-functions-for-javascript.html

    If only Javascript had real threading capabilities, our lives would be a little easier, until then, I think setTimeout is good - but it only makes things slightly less difficult than setInterval, in my opinion.

    For animations and fades and stuff, I quite like having a callback framework such as jQuery provides so that you don't have to maintain an animation loop.

  • It's not a good idea to rely on timed events in scripting languages inside of a browser.

  • One thing worth noting is that setTimeout waits for the function to finish executing, waits 5000ms and then call the function again.

    setInterval, on the other hand, will try to execute the function every 5000ms regardless of when the function was last called or how long the function takes to execute.

    Raj

  • Nice post. But you said SetInterval will be called on regualr basis whereas settimeout will be called only once. How can i use this in such a situation ?

    Thanks,
    Thani

  • @MisterFantastic: if you look at the code a little closer, you'll see that at the end of the callback code, setTimeout is called again for the next iteration.

  • If you have any complexity in your code, setInterval makes it that much more complex. You have to deal with unexpected behavior when one thread overwrites another's data. I did this in my early days and learned the hard way to use setTimeout and call it again just as the Evil blog guy says.

  • @Nicholas: that is true, although a setInterval occurrence won't interrupt JS code that may still be running, so there are cases where the real interval will be larger than the one specified. Still, for operations that are sensitive to precise timing, you can take two approaches. First, you can use setInterval and deal with the disadvantages, knowing that at least you get timing that is as precise as it can get in JS. Second, you can use setTimeout and move the renewal of the timeout to the top of the function. This way, the timing should be very close to the one you'd get from setInterval. I should probably mention that I didn't verify this yet. Might be an interesting follow-up post...

  • Hey, wanna say thanks - this helped me modify my code and it's workin great. I have an ajax-based page which shows and hides different div's, and one is a chat div. So I setup the function that polls the database for new chat messages to run once, and from inside that function it does a check to see if the chat div is visible, and if so, runs a setTimeout to rerun the check message function in 5 seconds.

    If the user moves away from the chat and on to something else (therefore hiding the chat div) the setTimeout doesn't trigger and the script stops polling the database for new chat messages until you click on the "chat" button again to reactivate it.

    Works perfectly! I was using a setInterval but much happier now.

  • @rajbk

    I do not believe setTimeout "waits" the CPU during the timeout period. It instead schedules it with a timer running in the background. This might cause it to take slightly more than the expected timeout period to start executing.

  • Hmm, maybe late to the party...

    Anyway I'm using this approach for a jquery cycling images type'o'thing but I just get StackOverflow errors with the recursive call.

    First time I ran this script it crashed firefox 4.1 with no recovery! Chrome however at least says 'you're script is gonna crash! shall I kill it?' (effectively).

    Am I doing something wrong?

  • @Chris: hard to say without seeing your code. But yes, you are obviously doing something wrong.

  • Trying to make a simple, multi-use slideshow and setInterval() is causing havoc; I'll try and use this method and hopefully all my problems will be solved. Thanks!

Comments have been disabled for this content.