I have a checkbox than can toggle certain behaviour, however if someone makes a 100 consecutive clicks I don't want to send 100 requests to my server side.
This is what I got in place so far (found this code snippet):
deBouncer = function($,cf,of, interval){
var debounce = function (func, threshold, execAsap) {
var timeout;
return function debounced () {
var obj = this, args = arguments;
function delayed () {
if (!execAsap)
func.apply(obj, args);
timeout = null;
}
if (timeout)
clearTimeout(timeout);
else if (execAsap)
func.apply(obj, args);
timeout = setTimeout(delayed, threshold || interval);
}
}
jQuery.fn[cf] = function(fn){ return fn ? this.bind(of, debounce(fn)) : this.trigger(cf); };
};
In my document ready function :
deBouncer(jQuery,'smartoggle', 'click', 1500);
Then the event itself :
$(window).smartoggle(function(e){
MyToggleFunction();
});
This works as I've put 1500 ms to be the debouncing period, so if you click n times withing 1500 ms it will send only the latest state to the server.
There is however side effect of using this, now my click event for other stuff is messed up. Am I doing something wrong here? Is there a better way to debounce?
I have a checkbox than can toggle certain behaviour, however if someone makes a 100 consecutive clicks I don't want to send 100 requests to my server side.
This is what I got in place so far (found this code snippet):
deBouncer = function($,cf,of, interval){
var debounce = function (func, threshold, execAsap) {
var timeout;
return function debounced () {
var obj = this, args = arguments;
function delayed () {
if (!execAsap)
func.apply(obj, args);
timeout = null;
}
if (timeout)
clearTimeout(timeout);
else if (execAsap)
func.apply(obj, args);
timeout = setTimeout(delayed, threshold || interval);
}
}
jQuery.fn[cf] = function(fn){ return fn ? this.bind(of, debounce(fn)) : this.trigger(cf); };
};
In my document ready function :
deBouncer(jQuery,'smartoggle', 'click', 1500);
Then the event itself :
$(window).smartoggle(function(e){
MyToggleFunction();
});
This works as I've put 1500 ms to be the debouncing period, so if you click n times withing 1500 ms it will send only the latest state to the server.
There is however side effect of using this, now my click event for other stuff is messed up. Am I doing something wrong here? Is there a better way to debounce?
Share Improve this question asked May 6, 2014 at 11:41 Gandalf StormCrowGandalf StormCrow 26.2k70 gold badges179 silver badges268 bronze badges 3- You are definitely on the write track here.. I would only bind this event to the checkmark in question though. That might let you simplify some of the other coding. – Jeremy J Starcher Commented May 6, 2014 at 11:46
- @JeremyJStarcher can you please give an example of what you mean? – Gandalf StormCrow Commented May 6, 2014 at 11:48
- I'd need to see your markup... or I could give you a made-up example based on how I do it. – Jeremy J Starcher Commented May 6, 2014 at 11:50
3 Answers
Reset to default 14Just debounce the function that does the actual work and I wouldn't load an entire library for this.
var debouncedSomeFunction = debounce(someFunction, 1500);
debouncedSomeFunction();
debouncedSomeFunction();
debouncedSomeFunction();
setTimeout(debouncedSomeFunction, 2000);
function debounce(fn, bufferInterval) {
var timeout;
return function () {
clearTimeout(timeout);
timeout = setTimeout(fn.apply.bind(fn, this, arguments), bufferInterval);
};
}
function someFunction() {
log('someFunction executed');
}
function log(text) {
document.body.appendChild(document.createTextNode(text));
document.body.appendChild(document.createElement('br'));
}
Not sure if there can be a "proper" way to do this.
Having said that underscore has such a utility that will create a debounced version of your function...
var MyToggleDebounced = _.debounce(MyToggleFunction, 1500);
then use MyToggleDebounced
in your click handler.
Link to debounce docs on underscorejs
Take a look at the annotated source for how they do it.
I think that this question is better than it seems first. There is a caveat in how Http Ajax requests work. If you set the delay to 1500ms and you can guarantee that each request is served under this time span than other answers will work just fine. However if any request gets significantly slow it then the requests may come out of order. If that happens, the last processed request is the one for which data will be displayed, not the last sent.
I wrote this class to avoid this caveat (in Typescript, but you should be able to read it):
export class AjaxSync {
private isLoading: boolean = false;
private pendingCallback: Function;
private timeout;
public debounce(time: number, callback: Function): Function {
return this.wrapInTimeout(
time,
() => {
if (this.isLoading) {
this.pendingCallback = callback;
return;
}
this.isLoading = true;
callback()
.then(() => {
this.isLoading = false;
if (this.pendingCallback) {
const pendingCallback = this.pendingCallback;
this.pendingCallback = null;
this.debounce(time, pendingCallback);
}
});
}
);
}
private wrapInTimeout(time, callback) {
return () => {
clearTimeout(this.timeout);
this.timeout = setTimeout(callback, time);
};
}
}
This will prevent two ajax-requests processed at the same time and this will send another request if there is a pending one.