
Debounce and throttle
Planted:
Tended:
Status: mature
Hits: 332
Intended Audience: Front-end developers
Debounce and throttle are two techniques to improve performance and UX (User Experience). Both limit how many times a function is invoked over a period of time.
MOVE MOUSE HERE
Events over time
Debounced events
Throttled events
Debounce
A debounce function takes a function and returns an optimized version of it. The optimized version groups a sudden burst of function calls into one.
How it works. When debouncedFunc
is invoked, a timer will start.
When the timer completes, func
is invoked.
If, debouncedFunc
is invoked again before the timer completes, the timer will restart.
index.js
function debounce(callback, waitMS = 200) {let timeoutId;return function(...args) {const context = thisclearTimeout(timeoutId);timeoutId = setTimeout(() => {timeoutId = nullcallback.call(context, ...args)}, waitMS);};};function func(x) {console.log(x);}const debouncedFunc = debounce(func)// Will be calleddebouncedFunc(1);// Won't be called because of debouncingdebouncedFunc(1);// Will be called because it is called after the debounce limit has expired from the initial call abovesetTimeout(() => debouncedFunc(1), 200);
There are two types of debounce implementations:
- ▪ Trailing edge: The above implementation.
func
is invoked AFTER the timer has completed. - ▪ Leading edge:
func
is invoked BEFORE the timer starts. When the timer completes,func
will not be invoked. Implementation below.
index.js
function debounce(callback, isLeadingEdge = false, waitMS = 200) {let timeoutId;return (...args) => {const context = thisconst isCallNow = isLeadingEdge && !timeoutIdclearTimeout(timeoutId);timeoutId = setTimeout(() => {timeoutId = nullif (!isLeadingEdge) {callback.call(context, ...args)}}, waitMS);if (isCallNow) {callback.call(context, ...args);}};};function func(x) {console.log(x);}const debouncedFunc = debounce(func, true)// Will be calleddebouncedFunc(1);// Won't be called because of debouncingdebouncedFunc(1);// Will be called because it is called after the debounce limit has expired from the initial call abovesetTimeout(() => debouncedFunc(1), 200);
Use case. Input validation. Imagine a form with an email input. Each time the user enters a character, a validation function is invoked. If the input value is not in a valid email format, it renders a error message. This UX isn't great. An error message is shown as soon as the first character is entered. To improve. Debounce the validation function. The validation function will only be invoked after the user has finished entering all characters of their email.
Throttle
A throttle function takes a function and returns an optimized version of it. The optimized version prevents a function being called more than once every x milliseconds.
How it works. When throttledFunc
is invoked, func
will be invoked and a timer will be started.
Until the timer completes, any calls to throttledFunc
will not invoke func
.
index.js
function throttle (func, waitMS = 200) {let isWait = false;return function(...args) {if (!isWait) {func.call(this, ...args);isWait = true;setTimeout(() => {isWait = false;}, waitMS);}}}function func(x) {console.log(x);}const throttledFunc = throttle(func)// Will be calledthrottledFunc(1);// Won't be called because of throttlingthrottledFunc(1);// Will be called because it is called after the throttle limit has expired from the initial call abovesetTimeout(() => throttledFunc(1), 200);
Use case. Window scroll callback. Imagine you set a callback for the window scroll event. It could be invoked up to 30 times per second. Depending on what the callback was doing, a lot of these could be redundant. While the browser is processing all them, it could block execution, making the browser unresponsive. Throttling the callback will reduce the number of invokes.
requestAnimationFrame
window.requestAnimationFrame
is a method provided by the browser that will throttle a function.
It requests the browser invokes a function before the next repaint.
requestAnimationFrame
can be thought of as a throttle with waitMS = 16
(60fps).
However, internally will decide the best timing on how to schedule the rendering.
It has a much higher fidelity than a common throttle being a browser native API that aims for better accuracy.
When a property like opacity is changed, the user won't see that change until the browser does a repaint.
Imagine you have an element with an opacity of 1.
You create a function that change that element's opacity.
You invoke the function twice, once to change it to 0.9
, then again the change it to 0.8
.
If both of those calls occur before the browser does a repaint, then the 1st will have no effect.
The user will only see the element change from 1
to 0.8
.
These unrequired invokes can be avoided using requestAnimationFrame
.
How it works. You pass a function to requestAnimationFrame
.
It won't be invoke until the browser is ready to make the next repaint.
Use case.
When a function is painting or animating properties and you want to guarantee smooth changes.
For example, changing the opacity of an element based on the scroll position.
Below, we use requestAnimationFrame
when a new value for opacity needs to be set.
If that value changes before a repaint, we cancel the request and set a new one with the latest value.
.call(..)
In the above examples, callback.call(..)
is used instead of callback(..)
.
This is to ensure the correct this
value is bound.
See JavaScript - this for details.
Feedback
Have any feedback about this note or just want to comment on the state of the economy?