1

Debouncing in Your Framework

 6 months ago
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.
neoserver,ios ssh client

Debouncing in Your Framework

mobilet2-light-1200x303.png?sfvrsn=61408e7d_3

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);
React TSX

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);
React TSX

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();
React TSX

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();
React TSX

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);
React TSX

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);
    };
}
React TSX

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();
React TSX

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()" />
React TSX

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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK