At a fundamental level it’s important to understand how JavaScript timers work. Often times they behave unintuitively because of the single thread which they are in. Let’s start by examining the three functions to which we have access that can construct and manipulate timers.
var id = setTimeout(fn, delay);
– Initiates a single timer which will call the specified function after the delay. The function returns a unique ID with which the timer can be canceled at a later time.var id = setInterval(fn, delay);
– Similar tosetTimeout
but continually calls the function (with a delay every time) until it is canceled.clearInterval(id);
,clearTimeout(id);
– Accepts a timer ID (returned by either of the aforementioned functions) and stops the timer callback from occurring.
In order to understand how the timers work internally there’s one important concept that needs to be explored: timer delay is not guaranteed. Since all JavaScript in a browser executes on a single thread asynchronous events (such as mouse clicks and timers) are only run when there’s been an opening in the execution. This is best demonstrated with a diagram, like in the following:
(Click to view full size diagram)
There’s a lot of information in this figure to digest but understanding it completely will give you a better realization of how asynchronous JavaScript execution works. This diagram is one dimensional: vertically we have the (wall clock) time, in milliseconds. The blue boxes represent portions of JavaScript being executed. For example the first block of JavaScript executes for approximately 18ms, the mouse click block for approximately 11ms, and so on.
Since JavaScript can only ever execute one piece of code at a time (due to its single-threaded nature) each of these blocks of code are “blocking” the progress of other asynchronous events. This means that when an asynchronous event occurs (like a mouse click, a timer firing, or an XMLHttpRequest completing) it gets queued up to be executed later (how this queueing actually occurs surely varies from browser-to-browser, so consider this to be a simplification).
To start with, within the first block of JavaScript, two timers are initiated: a 10ms setTimeout
and a 10ms setInterval
. Due to where and when the timer was started it actually fires before we actually complete the first block of code. Note, however, that it does not execute immediately (it is incapable of doing that, because of the threading). Instead that delayed function is queued in order to be executed at the next available moment.
Additionally, within this first JavaScript block we see a mouse click occur. The JavaScript callbacks associated with this asynchronous event (we never know when a user may perform an action, thus it’s consider to be asynchronous) are unable to be executed immediately thus, like the initial timer, it is queued to be executed later.
After the initial block of JavaScript finishes executing the browser immediately asks the question: What is waiting to be executed? In this case both a mouse click handler and a timer callback are waiting. The browser then picks one (the mouse click callback) and executes it immediately. The timer will wait until the next possible time, in order to execute.
Note that while mouse click handler is executing the first interval callback executes. As with the timer its handler is queued for later execution. However, note that when the interval is fired again (when the timer handler is executing) this time that handler execution is dropped. If you were to queue up all interval callbacks when a large block of code is executing the result would be a bunch of intervals executing with no delay between them, upon completion. Instead browsers tend to simply wait until no more interval handlers are queued (for the interval in question) before queuing more.
We can, in fact, see that this is the case when a third interval callback fires while the interval, itself, is executing. This shows us an important fact: Intervals don’t care about what is currently executing, they will queue indiscriminately, even if it means that the time between callbacks will be sacrificed.
Finally, after the second interval callback is finished executing, we can see that there’s nothing left for the JavaScript engine to execute. This means that the browser now waits for a new asynchronous event to occur. We get this at the 50ms mark when the interval fires again. This time, however, there is nothing blocking its execution, so it fires immediately.
Let’s take a look at an example to better illustrate the differences between setTimeout
and setInterval
.
setTimeout(function(){ /* Some long block of code... */ setTimeout(arguments.callee, 10); }, 10); setInterval(function(){ /* Some long block of code... */ }, 10);
These two pieces of code may appear to be functionally equivalent, at first glance, but they are not. Notably the setTimeout
code will always have at least a 10ms delay after the previous callback execution (it may end up being more, but never less) whereas the setInterval
will attempt to execute a callback every 10ms regardless of when the last callback was executed.
There’s a lot that we’ve learned here, let’s recap:
- JavaScript engines only have a single thread, forcing asynchronous events to queue waiting for execution.
setTimeout
andsetInterval
are fundamentally different in how they execute asynchronous code.- If a timer is blocked from immediately executing it will be delayed until the next possible point of execution (which will be longer than the desired delay).
- Intervals may execute back-to-back with no delay if they take long enough to execute (longer than the specified delay).
All of this is incredibly important knowledge to build off of. Knowing how a JavaScript engine works, especially with the large number of asynchronous events that typically occur, makes for a great foundation when building an advanced piece of application code.
This is an excerpt from my work-in-progress book: Secrets of the JavaScript Ninja. To be released Fall 2008.
Previous excerpt: Partial Function Application in JavaScript
Caustic Dave (February 24, 2008 at 9:55 am)
Thank you John. Very informative. Can’t wait until your new book comes out!
Karl Swedberg (February 24, 2008 at 11:28 am)
Excellent, John! I love “sneak peeks” at the forthcoming book. You mention clearInterval, so I wonder why you don’t mention clearTimeout as well. Are the two fundamentally different?
John Resig (February 24, 2008 at 12:12 pm)
@Karl: As far as I know,
clearInterval
is capable of working on either type of timer. Doing the following works just fine:var id = setTimeout(function(){ alert('hello'); }, 1000);
clearInterval(id);
I suspect that the naming of the multiple methods is just a convenience. Someone can correct me if I’m wrong but I don’t think it matters. I’ll probably mention
clearTimeout
as well, just for completeness.Steve Finkelstein (February 24, 2008 at 12:41 pm)
John,
Please do let us know when you’re next book is available for pre-sale. I just checked amazon and to my dismay it wasn’t found. I’m ready to pay for it now!
/sf
John Resig (February 24, 2008 at 1:09 pm)
@Steve: I definitely will – I suspect that it’ll be within the next month or so. Whenever it goes up for pre-sale it’ll coincide with the release of the book web site (which will allow for interactive feedback and discussion). I’m pretty excited, I think it’ll make for a really nice experience.
Rod Chapman (February 24, 2008 at 1:38 pm)
Excellently written and informative as always. I really appreciate how you present the overall theory behind the topics that you discuss (as well as the fact that jQuery actually has a coherent “world view”). I’m obviously looking forward to your next book(s) as well.
Dean Edwards (February 24, 2008 at 2:06 pm)
Doing lots of work with timers huh? Me too.
Mike Purvis (February 24, 2008 at 2:43 pm)
It’s occasionally useful to exploit the single-threadedness of JavaScript by using a 0ms timer to queue up code to execute *after* everything else currently in line.
In the Google Maps API, there are lots of user behaviours that will trigger more than one event. So you can end up with a whole bunch of handlers all queued up in a known order, and it’s helpful to have a given handler be able to throw its own cleanup code on the end of the queue without disrupting the execution of the other handlers.
Chris Anderson (February 24, 2008 at 2:44 pm)
On the browser-difference’s front, one of the quirks that was fun to learn, is that some browsers (notably IE 6) can treat some asynchronous XHR requests as synchronous, if and when the response is available in the local browser cache.
I had a complex application that was depending on some code to be evaluated in the time between the Ajax request firing and the response returning. It was working on all the browsers, and until I tried IE 6 in a staging environment (production cache headers, and a full local browser cache) I didn’t notice the bug. Just goes to show that the browser timing idiosyncrasies can have real side effects – don’t depend on the scheduler to give you a chance to setup dependencies for your asynchronous callbacks.
I haven’t tested extensively, but I wouldn’t be surprised to see some browsers treating short setTimeouts as synchronous in certain circumstances, as well.
Pete B (February 24, 2008 at 2:58 pm)
This seems to show that setInterval is the better choice for animation. Did I get that right?
Tim Kadlec (February 24, 2008 at 4:01 pm)
Good post John! Add my name to the list of people eagerly awaiting your next book. You mentioned above that you expect to be able to pre-order within the next month or so. When are you looking at actually having the book released?
John Resig (February 24, 2008 at 4:20 pm)
@Rod: Thanks, glad you enjoyed it!
@Dean Edwards: Increasingly so. I’m always surprised as to how useful they can be.
@Mike Purvis: I think you’re a mind reader because that’s the next section in my book. Chapter 4 is all about timers and I talk about how to achieve that type of queuing, handling processor-intensive tasks, and animations.
@Chris Anderson: Actually, the only strange quirk relating to timers and Internet Explorer is that when you do a
setInterval(fn, 0);
it converts into asetTimeout(fn, 0);
. You can get around this by just using ‘1’ instead (I discuss this in my section on ‘choosing the right minimum timer rate’).@Pete B: It really depends on the situation – and how the timers are actually being used.
setInterval
will, most likely, get you more ‘frames’ in the animation but will certainly tax your processor more. A lot of frameworks end up usingsetTimeout
since it degrades more gracefully on slower computers.@Tim Kadlec: I’d imagine that it’d come out this fall. Right now I’m writing a chapter every 10 days – meaning that I’ll be done with all 21 chapters by the end of August. I’ll still have more editing, proofing, etc. to do so I imagine that sometime in the fall will be most realistic (like September/October-ish). However, anyone who pre-orders the book will have full access to it via the web site long before a physical copy arrives. I fully expect that user feedback will help to improve the quality of this book more than what any single technical editor can provide.
Larry Marburger (February 24, 2008 at 5:56 pm)
You certainly seem to be on a timer kick lately. Very interesting to read. I suppose I’ve never had the pleasure of working on a project that required very precise or consistent timers. To some extent, I’ve just taken them for granted.
That’s what I love about coming here. More often than not, I get to take a trip into dark corners of JavaScript that I might not get to experience on my own.
Keep up the good work, John. I’m excited to see what the community site around your book is going to be like.
Hamish M (February 24, 2008 at 11:33 pm)
Very interesting stuff. Specifically the difference between the
setTimeout
andsetInterval
examples, it’s a subtle distinction, but one that could be very important if you need timing as precise as possible.I’m looking forward to your book, John. Thanks for sharing this.
Andy Stevens (February 25, 2008 at 12:19 pm)
What’s the easiest way to detect when a “tick” has been missed, so you can e.g. skip an animation frame accordingly? Just use a variable to keep track of (new Date()).getTime() each time the function is called (so it knows how long it’s actually been since the last call), or is there a neater way?
John Resig (February 25, 2008 at 12:38 pm)
@Larry and Hamish: Glad you enjoyed it!
@Andy: Yes, using the time to determine the stage of the animation is best. You should never rely upon the timer/interval working as you would expect it to – it’s simply a means to an end. The problem is that if you perform animations using just timers, and not the wall clock time, then your animations are bound by the CPU (your frame rate will be effected). Whereas if you do it purely based upon the actual time then it will finish exactly when you expect it to, always (albeit with a drop in quality on slower machines).
Abhi (February 25, 2008 at 2:16 pm)
Just to make sure I understood let me reiterate. If an interval handler is already queued up for execution and before it gets a chance to execute another interval fires, then the newly fired interval’s handler will be dropped. But if an interval handler is executing and while it is executing another interval fires then the newly fired handler will be executed immediately after the currently executing interval handler is done executing. Sorry if I sound too convoluted.
John Resig (February 25, 2008 at 3:29 pm)
@Abhi: Yes, that’s correct.
Just to note that in your second example “But if an interval handler is executing and while it is executing another interval fires then the newly fired handler will be executed immediately after the currently executing interval handler is done executing.” it may not be the absolute next thing to execute (since another timer or event may be already queued) but it’ll certainly occur after no artificial delay.
But, yes, you are correct in your assumption – and, yes, this is quite convoluted.
Abhi (February 26, 2008 at 1:12 am)
Do the interval handlers in any way influence the dropping of the interval handlers. Say I already have one interval handler foo queued up for execution. Before foo can execute say another interval with the handler bar fires. Will bar be discarded as an interval handler is already queued up for execution or as bar is a different handler from foo it will be queued up?
Larry Gerndt (February 26, 2008 at 1:47 am)
Was very glad to have come across this pearl of wisdom, and I look forward to the book, Mr. Ninja John.
Massimiliano (February 26, 2008 at 4:05 am)
But what’s the queueing priority between Timers and asynchronous events, (like click or XHR callbacks)?
Is there a difference between a Timer and an “external” asynchronous event, that an external event has a priority over a Timer?
Jay S (March 24, 2008 at 3:01 pm)
There is a countdown plugin of Jquery:
http://keith-wood.name/countdown.html
That’s good one, but doesn’t count up.
Is there any library that you know before I start with revising that library?
Dan (May 1, 2008 at 9:32 am)
hello,
im a javascript newbie.
im currently developing an AJAX based chat system.
im really in doubt on what to use between setInterval and setTimeout that will periodically checks for new messages and users that is currently online.
i’ve read that setTimeout is widely used.
so my question is which one do you think will be the best for me?
thanks