setInterval is (moderately) evil
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.