Photo by Barna Bartis on Unsplash
I recently had to clean the code we are using in DeckDeckGo and had notably to refactor singleton methods to stateless functions. One of these gave me a harder time and that's why, guess what, I came to the idea of this new blog post đ
What is debouncing?
Sure, whatâs âdebouncingâ ?
Letâs say you have implemented an <input/>
in your application which triggers an update into your database each time its content change. For performance reason and maybe even for cost reason (if for example you are using Google Firestore) you might not want to trigger a database update every single time a keyboard key is hit but rather perform a save only when needed. For example you might want to only perform the save when the user would mark a pause or when she/he has finished her/his interaction with the component.
Likewise, you may have a function in your application, which might be called multiple times in a row, for which you would rather like to consider only the last call.
That is what debouncing is for me, to make sure that a method is not called too often.
Debounce time
Commonly, in order to detect which functions should effectively be triggered, a delay between calls is observed. For example, if we are debouncing a function with a debounce time of 300ms, as soon as, or more than, 300ms between two calls are observed, the function will be triggered.
Vanilla Javascript
setTimeout and clearTimeout working together
There is currently no platform implementation of a standard âdebouncing functionâ supported across browsers (correct me if I am wrong of course đ
). Fortunately, Javascript provide both the ability to delay a functionâs call using setTimeout
and to cancel it using clearTimeout
which we could combine in order to implement our own solution.
export function debounce(func: Function, timeout?: number) {
let timer: number | undefined;
return (...args: any[]) => {
const next = () => func(...args);
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(next, timeout > 0 ? timeout : 300);
};
}
In the above code, our function (the one we effectively want to perform, passed as parameter func
) is going to be delayed (setTimeout
). Before effectively doing so, we first check if it was not already called before (using the timer
reference to the previous call) and if it was, we cancel this previous call (clearTimeout
) before effectively delaying our target.
We could for example validate this implementation with a simple test. We could call multiple times in a row a function which log a string to the console. If everything works well, the output should occur only one time.
const myFunction: Function = debounce(() => {
console.log('Triggered only once');
});
myFunction(); // Cleared
myFunction(); // Cleared
myFunction(); // Cleared
myFunction(); // Cleared
myFunction(); // Performed and will output: Triggered only once
If you wish to observe and test this in action, give a try to this Codepen.
RxJS
Good dog helping with the cleaning
The above solution with vanilla Javascript is pretty cool but what about achieving the same result using RxJS (the Reactive Extensions Library for JavaScript)? That would be pretty slick isnât it? Lucky us, RxJS offers out of the box a solution to debounce easily functions using Observables. Moreover, in my point of view, this solution is a bit cleaner and more readable.
The RxJS function we are going to use is debounceTime. As explained in the documentation, it delays values emitted by a source Observable, but drops previous pending delayed emissions if a new value arrives on the source Observable. To reproduce the same example as above and to create an observable, we could for example use a Subject
and triggers multiple times in a row next()
. If everything goes according plan, again, we should find only a single output in the console.
const mySubject: Subject<void> = new Subject();
subject.pipe(debounceTime(300)).subscribe(() => {
console.log('Triggered only once');
});
mySubject.next(); // Cleared
mySubject.next(); // Cleared
mySubject.next(); // Cleared
mySubject.next(); // Cleared
mySubject.next(); // Performed and will output: Triggered only once
Thatâs it, nothing more nothing else. No custom functions to write, RxJS just solves the debouncing for us.
If you wish to give it a try in action too, have a look at this other Codepen.
Notabene: in the above example I did not, for simplicity reason, took care of unsubscribing the Observable. Obviously if you would use this solution in a real application, please be careful about this.
Cherry on the cake đđ
In our open source project DeckDeckGo, we are using a small utils package across our applications and components called deckdeckgo/utils
(published to npm) which offers miscellaneous utilities. One of these being the vanilla Javascript debounce
function. Therefore, if you need a quick and dirty solution, be our guest and give it a try đ
đ https://github.com/deckgo/deckdeckgo/tree/master/webcomponents/utils
To infinity and beyond đ
David