Table of contents
throttle
and debounce
are two functions widely used in frontend applications to filter a stream of events. They both optimize the application and browser performance.
Debounce
Debounce function limits the execution of a function call and waits for a certain amount of time before running it again.
Imagine a search form. Whenever a user types something we can perform a network request to fetch the search results. However, performing a network request after each character is typed is too expensive.
We can optimize and reduce the count of API calls by debouncing logic. Here we monitor the delay user gives between two key presses. If this delay matches our threshold limit, then we make another API call.
You may find it irritating that the debouncing event waits before triggering the function execution until the events stop happening so rapidly. Why not trigger the function execution immediately, so it behaves exactly as the original non-debounced handler? But not fire again until there is a pause in the rapid calls.
Lodash supports this with two optional parameters:
- trailing: the function should be run after some time without running the function
- leading: the function is run immediately and then it’s not run even if we call it again
Another Example of Debounce is when resizing a (desktop) browser window, they can emit many resize events while dragging the resize handle. Debounce Resize Event Example
Throttle
Throttling is a technique, to limit the execution of an event handler function, even when this event triggers continuously due to user actions.
Infinite Scroll is a quite common example. The user is scrolling down your infinite-scrolling page. You need to check how far from the bottom the user is. If the user is near the bottom, we should request more content and append it to the page. Here debounce wouldn't be helpful. It only would trigger only when the user stops scrolling. With throttle, we are constantly checking how far we are from the bottom. Infinite Scrolling throlled
The demo below showcases a stream of events and how debounced and throttled handlers are called. This codepen really helps visualize how they work with different leading and trailing options.
Debounce Polyfill
function debounce(func, wait, option = { leading: false, trailing: true }) {
let timerId = null;
let lastArgs = null;
return (...args) => {
// if both leading and trailing are false then do nothing.
if (!option.leading && !option.trailing) return;
// if timer is over and leading is true
// then immediately call supplied function
// else capture arguments in lastArgs
if (!timerId && option.leading) {
func.apply(this, args);
} else {
lastArgs = args;
}
// clear timer so that next call is exactly after `wait` time
clearTimeout(timerId);
timerId = setTimeout(() => {
// invoke only if lastArgs is present and trailing is true
if (option.trailing && lastArgs) func.apply(this, lastArgs);
// reset variables as they need to restart new life after
// calling this function
lastArgs = null;
timerId = null;
}, wait);
}
}
Throttle Polyfill
const throttle = (func, wait, options = {leading: true, trailing: true}) => {
let timer = null
let lastContext = null
let lastArgs = null
return function(...args) {
// 1. if called within cool time, then store it for later call
if (timer !== null) {
lastContext = this
lastArgs = args
return
}
// 2. if other than cool time, execute it
if (options.leading !== false) {
func.call(this, ...args)
} else {
// save for trailing call if needed
lastContext = this
lastArgs = args
}
// 3. set a timeout to clear the cool, time
// and run the stored context
const timeup = () => {
if (options.trailing !== false && lastArgs !== null) {
func.call(lastContext, ...lastArgs)
lastContext = null
lastArgs = null
timer = setTimeout(timeup, wait)
} else {
timer = null
}
}
timer = setTimeout(timeup, wait)
}
}
Feel free to leave your thoughts in the comments.
I love to connect with new people. Hit me up on Twitter | Linkedin