// Returns a function, that, as long as it continues to be invoked, will not | |
// be triggered. The function will be called after it stops being called for | |
// N milliseconds. If `immediate` is passed, trigger the function on the | |
// leading edge, instead of the trailing. | |
function debounce(func, wait, immediate) { | |
var timeout; | |
return function() { | |
var context = this, args = arguments; | |
clearTimeout(timeout); | |
timeout = setTimeout(function() { | |
timeout = null; | |
if (!immediate) func.apply(context, args); | |
}, wait); | |
if (immediate && !timeout) func.apply(context, args); | |
}; | |
} | |
/* minified */ | |
function debounce(a,b,c){var d;return function(){var e=this,f=arguments;clearTimeout(d),d=setTimeout(function(){d=null,c||a.apply(e,f)},b),c&&!d&&a.apply(e,f)}} | |
/* usage */ | |
var myEfficientFn = debounce(function() { | |
// All the taxing stuff you do | |
}, 250); | |
window.addEventListener('resize', myEfficientFn); |
@anurbol Hey - that wasn't necessary at all!!! It is always good to have examples for discussion and learning processes! And as for myself: my comments are meant as question. Hope that comes through!
However: thank you for responding!
Well my deleted functions actually did throttling, not debouncing, and they also had a bug.
This is a simple debounce function I came with:
var debounce = (callback, wait = 250) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => callback(...args), wait);
};
};
// Testing it:
var d = debounce(() => console.log("CALLED"), 1000);
for (let i = 0; i < 10; i++) {
// call every 100 ms (change to 1500 to see that debouncing will not happen)
let timeOut = 100 * i;
const cb = () => {
const date = new Date();
console.log(`calling at ${date.getSeconds()}.${date.getMilliseconds()}`);
d()
}
setTimeout(cb, timeOut)
}
One of the common usages is implementing an auto-completion for an input that will not overwhelm the server with requests.
Aaand yes let's discuss!
Regarding the position of clearTimeout
: it's dead simple - we cancel the previous timer i.e. previously scheduled function (if any - otherwise it's a no-op) just before we create the new one. We do it to prevent the previously scheduled function from being executed, because we don't want that, we only want the function/callback to be executed once (e.g. after user ends typing into the input), if the calls are happening frequently enough. Placing clearTimeout
inside the scheduled function doesn't make sense, because this will lead to the situation when we clear the timeout too late, the function would have already been executed.
Hope that makes sense! If no, please ask, or maybe correct me.
If we write this in TS? what would be the type of callback and ...args
interface DebouncedFunction {
(): any;
cancel: () => void;
}
export const debounce = <F extends (...args: any[]) => ReturnType<F>>(
func: F,
wait: number,
immediate?: boolean
) => {
let timeout: number = 0;
const debounced: DebouncedFunction = function(this: void) {
const context: any = this;
const args = arguments;
const later = function() {
timeout = 0;
if (!immediate) func.call(context, ...args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = window.setTimeout(later, wait);
if (callNow) func.call(context, ...args);
};
debounced.cancel = function() {
clearTimeout(timeout);
timeout = 0;
};
return debounced as (...args: Parameters<F>) => ReturnType<F>;
};
If found that, when setting
immediate
totrue
the debounced function was not invoked at all.
This is because as soon as you get atif (immediate && !timeout)
thetimeout
variable was just set.
Maybe move that if statement to before settingtimeout
?
This 👆
My TypeScript solution:
function debounce(func: (...args: unknown[]) => unknown, delay = 200) {
let timeout: number;
return function (...args: unknown[]) {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), delay);
};
}
or maybe (also when running in node or web)
function debounce<T>(func: (...args: T[]) => unknown, delay = 200) {
let timeout: number | NodeJS.Timeout;
return function (...args: T[]) {
clearTimeout(timeout as number);
timeout = setTimeout(() => func(...args), delay);
};
}
I suggest adding to our debounce
function return type unknown
or I prefer typeof func
as shown on line 4 below:
function debounce<T>(
func: (...args: T[]) => unknown,
delay = 200
): typeof func {
let timeout: number | NodeJS.Timeout;
return function (...args: T[]) {
clearTimeout(timeout as number);
timeout = setTimeout(() => func(...args), delay);
};
}
Without it results may be faced with type conversion issues a la ts(2352).
As indicated above, in the main gist function, using immediate
will break the implementation
So, for such needs where we wanted to call the function immediately (leading edge instead of trailing)
Try this implementation https://youmightnotneed.com/lodash#function
@rowild I am so sorry for the embarassment I caused with my code. Deleted it. It was incorrect and also, I hadn't understand the true concept of debouncing.