VoIP Resources VoIP Fundamentals Developer Blog

Avoiding JavaScript setTimeout and setInterval Problems

by Nader Zeid

Software Engineer Nader Zeid shares a technique to establish determinacy in creating timed events with the setTimeout and setInterval functions in JavaScript.

When OnSIP's popularity exploded, much of the software needed work to accommodate the growing number of customers. Nader Zeid, Software Engineer at OnSIP, was tasked with improving the Admin Portal. Customers with over 100+ users would be faced with unbearable load times when they logged into OnSIP's Admin Portal. The cause of this problem was due to the server being overloaded. To fix this dilemma, Nader began writing some JS code that would speed up OnSIP's website. Instead of the server having all the work to do, it made sense to make the browser chip in and do some work, too. In order to make this colossal JavaScript app run correctly Nader had to overcome many obstacles.

I thought I’d take this blog opportunity to share with other JavaScript developers a technique to establish some determinacy in creating timed events with the setTimeout and setInterval functions. As mentioned in nice detail over here, the time interval argument of each of those functions really only establishes that the given function will execute after at least that amount of time. So a timed event can miss its target by literally any amount of time. In a large JavaScript application with several timed events, this phenomenon can disproportionately delay queued tasks or in the worst case lock the web page.

The Problem

The two images below display two runs of some example code. We print to the console recursively with setTimeout.

Print to the console recursively with setTimeout.

Notice that on multiple runs the output changes. Runs of each example function are apparently subject to different delays.

The Solution

We quite simply have to subject them to the same delays. We do this by setting exactly one global timed function and implementing a function queue such that when a function’s given time target is reached, we execute it.

Overview:

Scheduler.add(func, context, timer, once)
  • func: the function to be added to the queue and executed

  • context: the context in which the function is executed, as per func.apply(context)

  • timer: the minimum time delay for execution. This is multiplied with minimum to give the time in milliseconds

  • once: decides whether the function is executed once or repeatedly

Scheduler.remove(func, context)
  • func: the function to be removed from the queue
  • context : the context matching func

Scheduler.halt()

Terminates the scheduler.

In Use

var x = 0;
var example1 = function () {
  console.log('TEST1');
  if(x < 10) {
    Scheduler.add(example1, null, 2, true);
    x++;
  }
}

var example2 = function () {
  console.log('TEST2');
  if(x < 10) {
    Scheduler.add(example2, null, 4, true);
    x++;
  }
}

example1();
example2();

Notes:

  • The crux of the solution is minimizing the number of calls to setTimeout and setInterval. This way the JavaScript engine only needs to maintain the timing of one event, thereby increasing reliability.
  • Since all functions are funneled into the same queue, the order of function execution is guaranteed.
  • We set a minimum time interval for the scheduler and force input to some multiple of that minimum. Using multiples this way maintains that the given time interval is the least amount of time needed to pass for the given function to be executed. Feel free to test other ideas.
  • The code in its current form works in all major browsers.

Be aware that this idea is far from original. Nearly all large-scale JavaScript frameworks implement a similar event loop to handle this ubiquitous problem. The benefit of this solution is that you get to see it and play with it yourself. =)

Learn more about VoIP Fundamentals