About
A debounce is a function that:
- will wrap a callback function
- will not let the callback function execute for each event
- will let the callback function execute only when a certain amount of time has elapsed since the last seen event.
For instance, in the context of:
- an keypress event, the debounce function will let the callback function runs when the user does not press any key for a certain amount of time.
- an mousmove event, the debounce function will let the callback function runs when the user does not move the mouse for a certain amount of time.
debounce is said to come from the switch Contact bounce problem.
Usage
It's mostly used with event that triggers really often such as:
- change (typing, …)
For example, in a search box that uses the change event, you don't want to search for every keystroke (ie to not search for every character of the search term)
Debounce function explained
This section will explain and comment on a debounce function to show how it works.
A debounce is a function that:
- wraps a function (called the callback function)
- to be executed in a interval of time
- only if the debounce function is not called again in this interval.
In Javascript, a call to the debounce function:
- will schedule the target function to execute with the setTimeOut
- delete the previous one if the debounce function was called again during the interval
- let run the last schedule if the debounce function was not executed again during the interval
Function Signature
The signature of the debounce function has tree arguments:
- funcToDebounce: the callback function to debounce
- interval: the debounce interval in milliseconds where the debounce function should not be called to trigger the callback function execution.
- leadingExecution: if true, execute the function:
- before the interval (leading execution or immediate execution)
- vs after (trailing execution)
function debounce(funcToDebounce, interval, leadingExecution) {
The timer Id
To execute after a certain amount of elpased time, the debounce function uses the setTimeOut function.
To keep track of the scheduled execution of the target function, we store the scheduler identifier (the returned value of the setTimeOut) in a timerId variable.
let timerId;
Every time that the debounce function is executed, this variable is not reset because we returns a function (it's called the closure functionality of javascript).
Returned function
Calling debounce returns a new anonymous function. Thanks to Javascript, this function is a closure and keeps track of its environment variables. The timerId will then persist and be available to this function.
return function() {
Scheduling condition
This condition checks if the function was already schedule.
It checks if timerId is a number (ie not null or undefined) and if this is the case it means that there is already a schedule running.
let wasFunctionScheduled = (typeof timerId === 'number');
Delete/Clear the previous schedule
The line below is the grand trick of the debounce function where:
- we delete the previous scheduled execution
- if the debounce function is called during the interval.
clearTimeout will delete the previous schedule function if timerId is valid/set or do nothing otherwise
clearTimeout(timerId);
The function to schedule
funcToSchedule is a wrapper function that:
- wrap the callback function and execute it
- will be scheduled to run after a time interval with the setTimeOut function.
Steps:
- We first capture the execution parameters of the debounced function that we will pass to the execution function (apply)
- and its argument
let funcToDebounceThis= this, funcToDebounceArgs = arguments;
- The funcToSchedule
let funcToSchedule = function() {
// Reset
clearTimeout(timerId);
timerId = null;
// trailing execution happens at the end of the interval
if (!leadingExecution) {
// Call the original function with apply
funcToDebounce.apply(funcToDebounceThis, funcToDebounceArgs);
}
}
Schedule the function to run at interval
Schedule each time an execution of the function over the interval.
timerId = setTimeout(funcToSchedule, interval);
- If the debounce function is called again during this interval, it will be deleted with the previous clearTimeout
- Otherwise, it will run
Leading Execution
If:
- the callback function was not schedule before function entrance
- the leading execution mode is on
Execute the function..
if (!wasFunctionScheduled && leadingExecution) funcToDebounce.apply(funcToDebounceThis, funcToDebounceArgs);
End of the returned function
}
End of debounce function
}
Demo: Mouse location
- Printing the mouse location after a mousemove
function onMouseMove(e){
console.log(new Date().toLocaleString()+": Position: x: "+e.x+ ", y:"+ e.y);
}
- Debounce the onMouseMove function to not print for every mouve
let debouncedMouseMove = debounce(onMouseMove, 500);
- Call the debounced function on every mouse move
window.addEventListener('mousemove', debouncedMouseMove);
- Output: Move the mouse above the console below quickly without stopping, then stop, the last schedule event will then fire. ie when you:
- move your mouse during the interval defined (500ms), all previous scheduling are deleted
- stop moving the mouse for the interval defined (500ms), the last event will fire
Library
A debounce function is also generally available in utility library such as:
- debounce (used by react.dev)
Debounce vs Throttle vs Deferred
- A debounced function is called only once in a given period, delay milliseconds after its last invocation (the timer is reset on every call).
- Debounce is great for keypress events; when the user starts typing and then pauses you submit all the key presses as a single event, thus cutting down on the handling invocations.
- A throttled function is limited to be called no more than once every delay milliseconds.
- Throttle is great for realtime endpoints that you only want to allow the user to invoke once per a set period of time.
- A Deferred function defers updating the UI, and keeps showing the previous results until the new results are ready.
- Unlike debouncing or throttling, it doesn’t require choosing any fixed delay.
- If the work you’re optimizing doesn’t happen during rendering, use debounced or throttle.
- In React 1):
- as soon as the original re-render finishes,
- React starts working on the background re-render with the new deferred value.
- Any updates caused by events (like typing) will interrupt the background re-render and get prioritized over it.