Because Performance Matters

Performance Basics: Throttling

By Shawn MaustSeptember 26, 2017

Whenever you’re listening for events on the page, it’s important to make sure that the event listeners don’t get overwhelmed with processing incoming requests. Otherwise they can quickly become a bottleneck and cause an unnecessary performance hit.

Where this often turns into an issue is when you have listeners that fire off events in rapid succession, like for scroll or mousemove or keydown events. Since scroll events, for instance, can fire off at such a high rate, it’s critical to make sure that the event handler isn’t doing computationally expensive operations. Because if it is, it will be all the more difficult for the browser to keep up.

Throttling

A standard way of dealing with these kinds of situations—where you have an event listener, and need to do some expensive operations—is to move the expensive operations outside of the handler, and to then throttle the calls to this function.

Throttling, in this case, means you’re going to only allow so many calls to the function in a given period of time.

For instance, if you are doing something related to animation, and you want your frame rate to be 60 fps, you don’t need to calculate more than once every 16ms (1000ms/60fps). Because of this, you could throttle the listener so it calls the needed function no more than once in that time period. The period itself can be arbitraty–maybe you want a certain function called no more than once every 100ms—but throttling entails some kind of restriction is put in place.

Note: Throttling is different that debouncing, in which we wait till the end of a string of events to call our function. For instance, waiting until scrolling has stopped for a certain amount of time before triggering the related function. See Debouncing and Throttling Explained Through Examples, by David Corbacho, for some good illustrated examples of the difference between the two.

So how do we throttle? We’ll look at a few different options in this post.

setTimeout()

The first option, and the one that’s been used for quite a while is to use setTimeout to schedule the next callback at a certain point in the future. This would ensure that it isn’t called too frequently. For instance, if I wanted to listen to the scroll event, and throttle any calls it makes to a custom function so that the function is not called more that once every 16ms, I could do something like this:

// See https://developer.mozilla.org/en-US/docs/Web/Events/scroll

var lastScrollPosition = 0,
    tick = false;  // Track whether call is currently in process

function customFunction (scrollPos) {
  // Custom function that does something
  // based on the scroll position
}

window.addEventListener('scroll', function(e) {
  lastScrollPosition = window.scrollY;
  if (!tick) {
    setTimeout(function () {
      customFunction(lastScrollPosition);
      tick = false;
    }, 16)
  }
  tick = true;
});

You could also abstract this out into a generic function that takes a callback function and time as its arguments. This helper function could then be used to throttle whatever function you want without writing additional code for each one.

// See http://sampsonblog.com/simple-throttle-function/

function throttle (callback, limit) {
  var tick = false;
  return function () {
    if (!tick) {
      callback.call();
      tick = true;
      setTimeout(function () {
        tick = false;
      }, limit);
    }
  }
}

window.addEventListener("scroll", throttle(customFunction, 16));

requestAnimationFrame()

Another option that we have at our disposal is the requestAnimationFrame (rAF) route. This is especially helpful when the function we want to throttle is connected with animation or anything that will be painting to the screen. This is similar to the setTimeout option above, but instead of setting a specific number of ms to wait, we simply pass it to the browser to be processed on the next frame, whenever that may be.

// See https://developer.mozilla.org/en-US/docs/Web/Events/scroll

var lastScrollPosition = 0,
    tick = false;  // Track whether call is currently in process

function customFunction (scrollPos) {
  // Custom function that does something
  // based on the scroll position
}

window.addEventListener('scroll', function(e) {
  lastScrollPosition = window.scrollY;
  if (!tick) {
    window.requestAnimationFrame(function() {
      doSomething(lastScrollPosition);
      tick = false;
    });
    tick = true;
  }
});

3rd Party

Another option is to use a pre-built solution. Lodash, for instance, is a popular JS library that includes a built-in method for throttling, as well as other common utilities.

Conclusion

You may not use this technique a lot, but it is good to be aware of, especially if you’re using a lot of event listeners. Whether you use a pre-built library, or roll your own, throttling can be an essential tool in helping the browser stay on top of what it has to handle.

Resources