DEV Community

Mark
Mark

Posted on

Throttling and Debouncing in JavaScript

Throttling and Debouncing is involved where high-frequency events are triggered, such as resize, scroll, input, etc. The core idea of debounce and throttle is to add a control layer between events and functions to delay execution. The goal is to prevent frequent execution of certain operations within a certain period of time, which would lead to a waste of resources.

The control layer between events and functions is usually implemented in two ways:

  • Using a timer. Each time an event is triggered, it is determined whether there is already a timer.

  • Using timestamp difference. Record the timestamp of the last event trigger, each time an event is triggered, determine whether the difference (delay - (now - previous)) between the current timestamp and the last execution timestamp has reached the set delay time.

Debouncing

Definition

Debouncing is the process of executing a callback function after an event is triggered for a specified time. If the event is triggered again within the specified time, the timer is reset according to the last trigger.

When an event is continuously triggered, the event handling function will only execute once if no event is triggered within a certain period. If an event is triggered again before the set time arrives, the delay is restarted. As shown in the following figure, the handle function is not executed when the scroll event is continuously triggered. Only when no scroll event is triggered within 1000 milliseconds, the scroll event will be delayed and triggered.

Image description

Example - Autocomplete Input

Unoptimized Implementation

Search box suggestion prompts: When we input character, we may request data from an API. If there is no optimization, the API will be continuously triggered from the start of the input, which will inevitably lead to resource wastage. It's also not wise to frequently change the DOM element in html tree.

// Bad code 
<html>   
    <body>     
        <div> search: <input id="search" type="text"> </div>   
        <script>
            const searchInput = document.getElementById("search"); 
            searchInput.addEventListener('input', ajax);       
            function ajax(e) { 
                // mock         
                console.log(`Search data: ${e.target.value}`);       
            }     
        </script>   
   </body> 
</html>
Enter fullscreen mode Exit fullscreen mode

In the above code, we don't have any optimization. We use the ajax() method to simulate data requests. Let's see the result.

Image description

If a real API is being called, it would continuously hit the server-side interface from the moment of input. It could also easily trigger rate limiting measures of the API. For example, the API provided by Github has a maximum request limit per hour.

Debounce Optimization

The principle is to use a marker to determine whether there are multiple calls within a specified time. When there are multiple calls, the previous timer is cleared and the timing starts again. If there is no further call within the specified time, the passed-in callback function (fn) is executed.

function debounce(fn, ms) {   
    let timerId;    
    return (...args) => {
        if (timerId) {       
            clearTimeout(timerId);     
        }    

        timerId = setTimeout(() => {       
            fn(...args);     
        }, ms);   
    } 
}

Enter fullscreen mode Exit fullscreen mode

Usage

const handleSearchRequest = debounce(ajax, 500)  
searchInput.addEventListener('input', handleSearchRequest);
Enter fullscreen mode Exit fullscreen mode

This is much better. When users input stops, it takes the last input text as the data param to request the API, avoiding request too many times.

Throttling

Throttling is the execution of a callback function at a specified interval after an event is triggered.

When an event is continuously triggered, it ensures that the event handling function is called only once within a certain time period. A common analogy for throttling is like water flowing from a faucet. When the valve is opened, water flows down rapidly. In the spirit of frugality, we want to reduce the flow from the faucet, ideally letting it drip down at a certain interval according to our wishes. For example, when a scroll event is continuously triggered, the handle function is not executed immediately, but rather every 1000 milliseconds.

Image description

Example - Scroll to The Top

The page has many list items, and after scrolling down, we hope a 'Top' button appears that can take us back to the top when clicked. At this point, we need to get the distance from the scroll position to the top to determine whether to display the 'Top' button.

Unoptimized Implementation

<body>   
    <div id="container"></div>   
    <script>     
        const container = document.getElementById('container');
        window.addEventListener('scroll', handleScrollTop);
        function handleScrollTop() {       
            console.log('scrollTop: ', document.body.scrollTop);       
            if (document.body.scrollTop > 400) { 
                // Handler to show top button       
            } else {      
                // Handler to hide top button
            }     
        }   
    </script> 
</body>
Enter fullscreen mode Exit fullscreen mode

As we can see, without any optimization, a single scroll might trigger hundreds of times, and processing each one is evidently a waste of performance.

Image description

Throttling Optimization

Implement a simple throttle function, is very similar to debounce. The difference is that here we use a flag to determine whether it has been triggered. Once it has been triggered, incoming requests are directly terminated until the previous specified interval has elapsed and the callback function has been executed, then it accepts the next process.

function throttle(fn, ms) { 
    let flag = false;   
    return (...args) => {   
        if (flag) return;    
        flag = true;     
        setTimeout(() => {  
            fn(...args)      
            flag = false;  
        }, ms);   
    } 
}
Enter fullscreen mode Exit fullscreen mode

Usage

const handleScrollTop = throttle(() => {  
    console.log('scrollTop: ', document.body.scrollTop);  
        // todo: 
}, 500); 
window.addEventListener('scroll', handleScrollTop);
Enter fullscreen mode Exit fullscreen mode

This one is much better with the above one.

Two Ways to Implement Throttling: Timestamps and Timers

  1. Timestamp
var throttle = function(func, delay) {            
  var prev = Date.now();            
  return function() {                
    var context = this;                
    var args = arguments;                
    var now = Date.now();                
    if (now - prev >= delay) {                    
      func.apply(context, args);                    
      prev = Date.now();                
    }            
  }        
}        
function handle() {            
  console.log(Math.random());        
}        
window.addEventListener('scroll', throttle(handle, 1000));              
Enter fullscreen mode Exit fullscreen mode
  1. Timers
var throttle = function(func, delay) {            
    var timer = null;            
    return function() {                
        var context = this;               
        var args = arguments;                
        if (!timer) {                    
            timer = setTimeout(function() {                        
                func.apply(context, args);                        
                timer = null;                    
            }, delay);                
        }            
    }        
}        
function handle() {            
    console.log(Math.random());        
}        
window.addEventListener('scroll', throttle(handle, 1000));
Enter fullscreen mode Exit fullscreen mode

Summary

Function Debounce: Combines multiple operations into a single operation. The principle is to maintain a timer, which triggers the function after a delay time. However, if it is triggered again within the delay time, the previous timer will be cancelled and reset. In this way, only the last operation can be triggered.

  • Window Resize Event, It is used when window size adaptation is needed. In this case, debounce is generally used because only the last change needs to be considered.

  • Search Input Event, If the input interval is greater than a certain value (such as 500ms), it is considered that the user has finished inputting and then starts searching.

Function Throttle: Ensures that a function is only triggered once within a certain period. The principle is to trigger the function by determining whether a certain time has been reached.

  • Scroll to Top
  • Search Input Event, for example, to implement real-time search input, throttle can be used.

Difference: Function throttling ensures that no matter how frequently the event is triggered, the actual event handling function will be executed once within a specified time, while function debounce only triggers a function after the last event. For example, in the scenario of infinite page scroll loading, we need the user to send an Ajax request every once in a while when scrolling the page, instead of requesting data only when the user stops scrolling. This scenario is suitable for the use of throttling technology.

Top comments (1)

Collapse
 
andrew-saeed profile image
Andrew Saeed

Very informative, Mark! Nicely done!