Stay safe, wrap your expensive callbacks with debounce

The debounce function is useful if you have a callback attached to an asynchronous event and you want that callback executing some time after a burst of events. Time intensive operations could halt our application, so it is good practice wrap those callbacks with a debounce.

The debounce function accepts three parameters, the callback, the wait time and an immediate flag which determines if we execute on the leading or trailing edge.

function debounce(callback, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) callback.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) callback.apply(context, args);
};
};

setTimeout returns a timerID which uniquely identify it. The timerID can be supplied to clearTimeout(timerID), which negates the setTimeout. The timerID is larger than zero and so is ‘truthy’. Debounce is a function factory that maintains reference to the the timeout variable in a closure scope, therefore all created functions have access to the timeout variable. The timeout variable serves as both a Boolean flag and a unique reference to the previous setTimeOut execution. Every-time another callback is executed, the previous setTimeout is cleared and another one is executed right after.

To illustrate the usefulness of debounce, lets write a function that logs time and string argument and wrap it with the debounce decorator.

function print(string){
time = new Date()
console.log(string,time.getUTCMilliseconds())
}
debouncedPrint = debounce(print,100)
debouncedPrint(‘first’)
debouncedPrint(‘second’)
debouncedPrint(‘third’)
debouncedPrint(‘fourth’)
// > fourth 319
// only the last callback was executed

Furthermore, lets write a queuing function, which guarantees that the callback will be executed with at least a specified time separation.

function queue(func,time){
var step = time
return function(){
var args = arguments, context = this
time += step
setTimeout(function(){func.apply(context,args)},time)
}
}
// lets wrap the debounced print with the queue decorator giving a separation of 150
printSeparatedby150 = queue(debouncedPrint,150)
printSeparatedby150(‘first’)
printSeparatedby150(‘second’)
printSeparatedby150(‘third’)
printSeparatedby150(‘fourth’)
// >first 282
// >second 431
// >third 582
// >fourth 732
// since the callbacks where called with a separation of greater than 100, the debounce time limit, each callback was executed

Now lets decrease the separation to less that the debounce time limit

printSeparatedby50 = queue(debouncedPrint,99)
printSeparatedby50(‘first’)
printSeparatedby50(‘second’)
printSeparatedby50(‘third’)
printSeparatedby50(‘fourth’)
// > fourth 672
// which is expected