Fundamentally, it’s important to understand how JavaScript timers work. Many times, their behavior is not intuitive because they are in a single thread.

Timer function

Let’s start by looking at three functions that we have access to that construct and manipulate timers.

  • var id = setTimeout(fn, delay)

    Starts a timer that calls the specified function after a delay. This function returns a unique ID that can be used to cancel the timer later.

  • var id = setInterval(fn, delay)

    Similar to setTimeout, but this function will be called repeatedly (each time with a delay) until it is cancelled.

  • clearInterval(id), clearTimeout(id)

    Accepts the timer ID (returned by any of the above functions) and stops the timer callback from occurring.

How does the timer work internally

To understand how timers work internally, an important concept needs to be explored: the amount of time that timers delay is indeterminate.

Because all JavaScript in the browser executes on a single thread, asynchronous events (such as mouse clicks and timers) run only when an empty position appears in the execution process. As follows:

The vertical direction represents time in milliseconds. The blue box represents the part of JavaScript that is executing. For example, the first block of JavaScript executes for about 18 milliseconds, the mouse-click block executes for about 11 milliseconds, and so on.

Because JavaScript can only execute one piece of code at a time (due to its single-threaded nature), each of these blocks blocks the process for other asynchronous events. This means that when an asynchronous event occurs (such as a mouse click, timer firing, or XMLHttpRequest completion), it will queue up for execution later (how this queuing actually happens will definitely vary by browser, so consider this).

First, in the first block of JavaScript, two timers are started: a 10mssetTimeout and a 10ms setInterval. Because of the time and place the timer started, it actually fired before we finished the first code block. Note, however, that it does not perform immediately (it cannot because of the thread). Instead, delayed callback functions are queued for execution at the next available time.

Also, in the first JavaScript block, we see the mouse click occur. The JavaScript callback associated with this asynchronous event (we never know when the user can perform the action, so it is considered asynchronous) cannot execute immediately, so, like the initial timer, it is queued up to execute later.

Immediately after the initial JavaScript block is executed, the browser asks the question: What is waiting to be executed? In this case, both the mouse-click handler and the timer callback are waiting. The browser then selects one (mouse click callback) and executes it immediately. The timer will wait for the next possible time for execution.

Note that the first interval callback executes while the mouse click handler is executing. Like timers, interval callbacks are queued up for execution later. Note, however, that when interval fires again (while the timer handler is executing), the handler execution is deleted. If all interval callbacks were queued while executing a chunk of code, the result would be a bunch of interval executions, with no delay between them, after completion. Instead, browsers tend to simply wait until no more interval handlers queue up (for problematic intervals), and then do more queuing.

In fact, we can see that this is the case when the third interval callback is fired while the interval itself is executing. This shows us an important fact: Intervals doesn’t care what is currently being executed, and they queue indiscriminately, even if it means sacrificing time between callbacks.

Finally, after the second interval callback execution is complete, we can see that the JavaScript engine has nothing to execute. This means that the browser is now waiting for new asynchronous events to occur. When the interval fires again, we get this at the 50ms marker. This time, however, there is nothing to prevent it from executing, so it triggers immediately.

Difference between setTimeout and setInterval

Let’s look at an example to better illustrate the difference between setTimeout and setInterval

setTimeout(function(){
  /* Some long block of code... */
  setTimeout(arguments.callee, 10)
}, 10)
 
setInterval(function(){
  /* Some long block of code... */
}, 10)
Copy the code

At first glance, the two pieces of code appear to be functionally equivalent, but they are not. It is worth noting that the setTimeout code always has a delay of at least 10 milliseconds after the last callback execution (it may end up being more, but never less), whereas the setInterval code attempts to execute a callback every 10 milliseconds, regardless of when the last callback was executed.

conclusion

We’ve learned a lot here, so let’s review:

  • The JavaScript engine has only one thread, forcing asynchronous events to queue for execution.
  • setTimeoutandsetIntervalThe way asynchronous code is executed is very different.
  • If the timer is prevented from executing immediately, it will be delayed to the next possible execution point (which will be longer than the required delay).
  • If the intervals are long enough (longer than the specified delay), they can be executed one after another without delay.

All of this is very important knowledge. Understanding how JavaScript engines work, especially when dealing with the large number of asynchronous events that typically occur, can provide a good foundation for building advanced application code.