Debouncing in Your Framework
source link: https://www.telerik.com/blogs/debouncing-your-framework
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Debouncing in Your Framework
Debouncing is a way of skipping input garbage and waiting for things to calm before running a function. Make one debounce function and reuse it often.
Sometimes when you write a program, you need to wait a few seconds before doing anything. This is certainly the case when fetching data, re-fetching data and updating data. You may have 50 triggers at once, when all you need is one.
Debouncing is a way of skipping all this input garbage and waiting for things to calm. Then, and only then, will the expected function run.
Timer
The best example and way to show debouncing is with a timer. Let’s say we want to run the function getData()
at the appropriate time.
Step 1: setTimeout
First we need to wait a good time, say 5 seconds, before running the function.
setTimeout(() => console.log('5 seconds'), 5000);
This will make sure nothing gets run until 5 seconds pass. This is great, but we may need to cancel this.
Step 2: clearTimer
const timeout = setTimeout(() => console.log('5 seconds'), 5000);
clearTimeout(timeout);
If we run this, it will allow us to clear the timeout. This means we are canceling running the function inside setTimeout
However, since we don’t have any mechanisms to continue, it will always get cleared.
Step 3: Function
Here we put the timeout inside of a function. If the timeout exists, it will get cleared out. Notice we have to declare the timeout
variable outside of the run
function in order for it to persist. Otherwise it would just declare a new timeout every time and would never get canceled.
let timeout: NodeJS.Timeout;
const run = () => {
clearTimeout(timeout);
timeout = setTimeout(() => console.log('5 seconds'), 5000);
};
run();
run();
run();
Since run
is ran three times in a row, the timeout does not have time to complete. It will get run before 5 seconds has elapsed. The first two run
functions get canceled, and the third one runs as expected. This is what prevents extraneous calls. This is called debouncing.
Step 4: Debounce
If we are using this one time in our program and we never thing we will ever use this function again (highly unlikely for any web programmer), then we could just stop here:
let timeout: NodeJS.Timeout;
const debounce = () => {
clearTimeout(timeout);
timeout = setTimeout(() => getData(), 5000);
};
debounce();
debounce();
debounce();
However, we would be breaking Single Responsibility Principal
, where we could easily make this a module and reusable. I have found after more than 20 years of programming, this one principal saves me the most time.
Step 5: useDebounce
The problem with creating a reusable module is the timeout variable. We need to make sure it gets reused. We also don’t want to re-pass the timeout and function.
DON’T DO THIS!!!
let timeout: NodeJS.Timeout;
const getData = () => {
console.log('getting data...');
};
const debounce = (func: () => void, waitfor: number) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(), waitfor);
};
debounce(getData, 5000);
debounce(getData, 5000);
debounce(getData, 5000);
So we need a function we can import, set up our variables, then call whenever and as many times as we like:
useDebounce.ts
export function useDebounce<F extends (...args: Parameters<F>) => ReturnType<F>>(
func: F,
waitFor: number,
): (...args: Parameters<F>) => void {
let timeout: NodeJS.Timeout;
return (...args: Parameters<F>): void => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), waitFor);
};
}
We can import this function anywhere we like, and reuse it in any framework!
Step 6: Usage
import { useDebounce } from 'use-debounce';
const getData = () => {
console.log('getting data...');
};
const debounce = useDebounce(getData, 5000);
debounce();
debounce();
debounce();
As you can see, we get reusable code with the same results. No need to keep track of timers or repass our data.
Example
Autosave
One usage maybe for autosaving drafts:
const saveDraft = () => {
// save draft
};
const debounce = useDebounce(saveDraft, 5000);
...
<input type="text" oninput="debounce()" />
You may see onchange
or onkeyup
as well here. This would be the same pattern for:
- Search suggestions as you type
- Dropdown autocomplete
This generally would be a good idea for anything you have to calculate as well. If you have a game, you may not want to recalculate the position until a player gets done moving. You will see many examples with mouse movements. There are endless use cases.
Other Notes
- RXJS has a debounce used for observables. This is great when you want to cancel a subscription as you get more coming in.
- Lodash has this built in, but I’m not a fan of importing more than you need.
Getting debounce to work is not always as straightforward as it seems. You must declare a function that returns a function. Also, getting types in TypeScript to align with ESLint may not be as evident in other versions. I tried to give a version that can work anywhere.
Debouncing is a necessity for any intermediate or above programmer. Use and reuse this function, and you will never have to waste time again thinking about it.
How to Create Great Pull Requests
Take these simple steps to create great pull requests, particularly when working with large teams with multiple developers.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK