Javascript event execution deferred in a queue - javascript

I'm trying to explain my problem to know the better way to solve it. I've searching a bit, but I don't know how to search exactly:
I have an HTML page with three areas: Panel A, Grid B and Grid C.
On grid C, I can do an action on a row (only clicking it) that updates some counters on panel A and Grid B, but they're calculated on database totals.
When I do the row action I update the row immediately and trigger an event listened by Panel A and Grid B which sends both requests against the server to update it's counters.
Every row update is a bit heavy and if the user clicks various rows fast, the javascript execution is locked flooding the server with updates of Panel A and Grid B which could be deferred to execute only one time if on 1 or 2 seconds the event is not triggered.
I would solve the problem on the listenTo callback because it could be another panel that the event action must be performed "immediately".
I imagine something like this (only refresh after 2 seconds of no event listened), but I think that there must be a better way:
var eventTimeout = {}; // one for listener
element.bind('eventName' function() {
if (eventTimeout['eventName']) {
clearTimeout(eventTimeout['eventName']); // I understand that if the timeout has been exhausted no error is thrown
}
eventTimeout['eventName'] =
setTimeout(function() {
eventTimeout['eventName'] = null;
doAction();
}, 2000);
});
I'll go away with that implementation (I haven't tested yet), when I have more time, I'll put it on a JSFiddle to help to understand.

You are on the right track with your code but you may want to use something like lodash-throttle function decorators rather than reinventing the wheel here IMO.
lodash Throttle
Creates a throttled function that only invokes func at most once per every wait milliseconds. The throttled function comes with a cancel method to cancel delayed invocations. Provide an options object to indicate that func should be invoked on the leading and/or trailing edge of the wait timeout. Subsequent calls to the throttled function return the result of the last func call.
examples from their own site:
// avoid excessively updating the position while scrolling
jQuery(window).on('scroll', _.throttle(updatePosition, 100));
// invoke `renewToken` when the click event is fired, but not more than once every 5 minutes
jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
'trailing': false
}));
// cancel a trailing throttled call
jQuery(window).on('popstate', throttled.cancel);

Using the previous #bhantol very valuable response, and some other stackoverflow responses (https://stackoverflow.com/a/43638411/803195) I've published a sample code that simulates the behavior I actually want.
Perhaps it was not well defined on initial question, but I need actually use debounce and it must be dynamic, depending on some variables (a checkbox on the following sample) it must be "delayed" or "immediate":
https://codepen.io/mtomas/pen/xYOvBv
var debounced = _.debounce(function() {
display_info($right_panel);
}, 400);
$("#triggerEvent").click(function() {
if (!$("#chk-immediate").is(":checked")) {
debounced();
} else {
display_info($right_panel, true);
}
});
The sample is based on a original sample published on that (interesting) article:
https://css-tricks.com/debouncing-throttling-explained-examples/
-- UPDATE --
Using debounce of lodash implies me to import full lodash (72Kb minimized), so I've implemented a "lite" own debounce using this reference:
https://davidwalsh.name/function-debounce
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
I've updated my codepen test too.

Related

How to stop the previous instances of the same function if it's called multiple times?

I have written a custom animation function. It usually works just fine, but when I call animate(); in rapid succession with different endCallbacks, sometimes the callbacks overlap really badly, causing the wrong action at the wrong time.
The problem is that the function instantiates multiple times and executes untill the endValue is reached. The currentValue is changed so fast that I get to see just the last value in my html page animation. This hiddes this unwanted behavior.
What I need when I call animate(); a second time is to end the first instance of animate(); and trigger a new one with new values and a new callback. Also at the same time I want to stop the setTimeout() function just to make sure no wrong callback is triggered.
window.onload = function(){
document.addEventListener('click', // some button
function (){
animate(1, 10);
}, false
);
}
function animate(startValue, endValue, callback, endCallback) {
var startValue = startValue,
currentValue = startValue,
endValue = endValue,
callback = callback,
timeout = null;
loopAnimation();
function loopAnimation(){
if (currentValue != endValue){
timeout = setTimeout(function(){
currentValue++;
// Callback executes some page manipulation code
if (typeof callback !== "undefined") callback(currentValue);
console.log(currentValue);
loopAnimation();
},500)
} else {
console.log("This callback triggers some specific changes in my page");
if (typeof endCallback !== "undefined") endCallback();
}
}
}
Instead of seeing in the console:
1,2,3, - 1,4,2,5 ... 6,9,7,10,8,9,10
I'd like to see just:
1,2,3, - 1,2 ... 7,8,9,10
However, keep in mind that because of the way I use animate() in my script I can't relly on knowing the name or scope of the input variables. This cuts me from being able to solve it myself.
While it isn't quite the implementation you're asking for, I wonder if Underscore's throttle or debounce would meet the need?
debounce will make sure your function is called no more than X times per second -- it'll still be executed once per every time called, but the subsequent calls will be delayed to meet your rate limit. So if you called animate twice in quick succession, debounce can delay the second execution until 100ms after the first or what have you.
throttle will basically ignore calls that occur during the rate limit. So if you call your animate 10 times within 100ms, you could have it throw out all but the first. (Actually, it'll do the first one, plus one at at the end of the wait period).
You don't need to use all of underscore to get these methods; I've seen people frequently copy and pasting just the debounce and/or throttle functions from underscore. If you google, you can find some standalone throttle or debounce implementations.
Throttle and debounce are commonly used in just your case, animation.
For your original spec, to actually "end the first instance of animate()" -- there's no great reliable way to do that in javascript. There's no real general purpose way to 'cancel' a function already being executed. If you can make it work with debounce or throttle, I think it will lead to less frustration.
What you need is to store the last timeout id you used. So next time you start a new animation, you clear any ongoing animation using this timeout id and clearTimeout.
I found convenient to store the interval on the function itself.
See the jsbin here :
http://jsbin.com/nadawezete/1/edit?js,console,output
window.onload = function(){
document.addEventListener('click', // some button
function (){
animate(1, 10);
}, false
);
};
function animate(startValue, endValue, callback, endCallback) {
var currentValue = startValue;
if (animate.timeout) clearTimeout(animate.timeout);
loopAnimation();
function loopAnimation(){
if (currentValue != endValue){
animate.timeout = setTimeout(function(){
console.log(currentValue);
currentValue++;
// Callback executes some page manipulation code
if (callback ) callback(currentValue);
loopAnimation();
},500);
} else {
console.log("This callback triggers some specific changes in my page");
if (endCallback) endCallback();
}
}
}

Prevent and queue action (but only once globally) if previously called within X seconds

I always run into this problem and seem to implement a nasty looking solution.
It seems like a common design pattern to fire an action immediately, but not let that action queue up if clicked rapidly / delay firing if previously called within a timeframe. In my real world example, I have an AJAX call being made, so if I don't prevent repetitive actions the browser queues requests.
How would you implement this differently? What other options are there?
function myFunction() {
console.log("fired");
}
var timeout = null;
$("#foo").click(function() {
// if not previously clicked within 1 second, fire immediately
if (!timeout) {
myFunction();
timeout = setTimeout(function() {
timeout = null;
}, 1000);
} else {
// clicked again within 1s
clearTimeout(timeout); // clear it - we can't have multiple timeouts
timeout = setTimeout(function() {
myFunction();
timeout = null;
}, 1000);
};
});
With your current code, if you repeatedly click "#foo" at an interval slightly less than one second, say every 800ms, on first click it will fire the function immediately (obviously), but then it will fire the function exactly once more one second after the last click. That is, if you click ten times at 800ms intervals the function will fire once immediately and a second time approximately 8 seconds (800ms * 9 + 1000ms) after the first click.
I think you're better off removing the else case altogether, so that on click it will fire the function if it has not been called within the last second, otherwise it will do nothing with no attempt to queue another call up for later. Not only does that seem to me like a more logical way to operate, it halves the size of your function...
On the other hand, since you mentioned Ajax, rather than disabling the function based on a timer you may like to disable the function until the last Ajax request returns, i.e., use a flag similar to your timerid and reset it within an Ajax complete callback (noting that Ajax complete callbacks get called after success or failure of the request).
In the case of an auto-complete or auto-search function, where you want to send an Ajax request as the user types, you might want to remove the if case from your existing code and keep the else case, because for auto-complete you likely want to wait until after the user stops typing before sending the request - for that purpose I'd probably go with a shorter delay though, say 400 or 500ms.
Regarding general structure of the code, if I wanted a function to be fired a maximum of once per second I'd likely put that control into the function itself rather than in a click handler:
var myFunction = function() {
var timerid = null;
return function() {
if (timerid) return;
timerid = setTimeout(function(){ timerid=null; }, 1000);
// actual work of the function to be done here
console.log("myFunction fired");
};
}();
$("#foo").click(function() {
myFunction();
});
The immediately invoked anonymous function that I've added makes it uglier, but it keeps the timerid variable out of the global scope. If you don't like that obviously you could simply declare timerid in the same scope as myFunction() as you currently do.
This answer is getting kind of long, but if you have a lot of different functions that all need some kind of repeat control in them you could implement a single function to handle that part of it:
function limitRepeats(fn, delay) {
var timerid = null;
return function() {
if (timerid) return;
timerid = setTimeout(function(){ timerid = null; }, delay);
fn();
};
}
// myFunction1 can only be called once every 1000ms
var myFunction1 = limitRepeats(function() {
console.log("fired myFunction1()");
}, 1000);
// myFunction2 can only be called once every 3000ms
var myFunction2 = limitRepeats(function() {
console.log("fired myFunction2()");
}, 3000);
$("#foo").click(function() {
myFunction1();
myFunction2();
});

Fire event after scrollling scrollbars or mousewheel with javascript

I would like to know if it is possible to fire an event after the scrolling of a page, when using the scrollbar or mouse-wheel (or with a swipe on a touch device).
Basically, I'd like to detect when the user has stopped scrolling so I can then AJAX-load, rather than loading while scrolling.
It seems that jQuery's .scroll() is firing every time a user scrolls, and it seems clunky to have an event fire all the time. Is there such thing as .onScrollAfter(), synonymous to the .onMouseUp()?
I'd like to know whether this is possible (or if a function already exists) without using a framework, though I would consider one; especially jQuery.
This event does not exist. You can emulate it by using timeouts:
Example (concept code):
(function() {
var timer;
/* Basic "listener" */
function scroll_finish(ev) {
clearTimeout(timer);
timer = setTimeout(scroll_finished, 200, ev);
//200ms. Too small = triggered too fast. Too high = reliable, but slow
}
window.onscroll = scroll_finish; // Or addEventListener, it's just a demo
// Fire "events"
var thingey = [];
function scroll_finished(ev) {
// Function logic
for (var i=0; i<thingey.length; i++) {
thingey[i](ev);
}
}
// Add listener
window.addScrollListener = function(fn) {
if (typeof fn === 'function') {
thingey.push(fn);
} else {
throw TypeError('addScrollListener: First argument must be a function.');
}
}
window.removeScrollListener = function(fn) {
var index = thingey.indexOf(fn);
if (index !== -1) thingey.splice(index, 1);
}
})();
Thought I would add this as an answer even though it's old. The event you are trying to recreate I believe is synonymous to debounce. This is available in underscore.js
debounce_.debounce(function, wait, [immediate])
Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed since the last time it was invoked. Useful for implementing behavior that should only happen after the input has stopped arriving. For example: rendering a preview of a Markdown comment, recalculating a layout after the window has stopped being resized, and so on.
So it will wait after your last execution of the specific event. if you do not want a delay, you can just specify 0. David Walsh has a pretty nice implementation you can include in any project.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
Which you can go ahead adding by doing
var myEfficientFn = debounce(function() {
// All the taxing stuff you do
}, 250);
window.addEventListener('scroll', myEfficientFn);
Description
You can use the nice jQuery plugin Special scroll events for jQuery by James Padoley.
Works really great.
Check out the page and this jsFiddle Demonstration (Just scroll ;))
More Information
Special scroll events for jQuery
jsFiddle Demonstration

jQuery / backbone.js - delay function call

I have a #search element, which when the keyup event occurs should fire a function. This function should only fire if keyup hasn't occurred in a set amount of time (say 500 milliseconds for example). This will prevent search results from updating every letter that is pressed. The problem is that with backbone.js, I have my events in a hash and the one that is applicable looks like:
'keyup #search' : 'setSearch'
which calls the setSearch() function when the keyup event occurs. I'm not really clear on how to handle it at this point. I've tried a variety of things, but nothing can maintain the timer past the function ending.
I have something like so:
setSearch: function(event) {
var timer = window.setTimeout( function() {
// run function here
alert('fired');
}, 500);
},
rather than the alert('fired'), I'll have my own function run. I can see why this code doesn't work (a timer is set for every keyup event that occurs. But I still don't have a clear idea on what else I could try.
What you are looking for is actually a function provided to you from underscore.js (a requirement of Backbone)
setSearch: _.throttle(function() {
//Do Stuff
}, 500),
In a nutshell, this returns a new form of the anonymous function that can only be called once every 500ms. You will likely have to tweak the timing to your needs.
More Info:
http://documentcloud.github.com/underscore/#throttle
You need an instance variable in your view that stores the timer ID, then you can stop it and restart it as needed:
setSearch: function(event) {
var self = this;
if(self.timer)
clearTimeout(self.timer);
self.timer = setTimeout(function() {
alert('fired');
self.timer = null;
}, 500);
}
So, if the timer is already running, you call clearTimeout to stop it, start a new timer, and store the timer ID in self.timer (AKA this.timer). You'll also want to reset the stored timer ID in the timer's callback function or your setSearch won't do anything after its timer has fired once. And all the self business is just to capture this for use in the timer's callback function.
Preventing the updating of search results on every keyup is exactly the kind of situation that Underscore's _.debounce(function, wait) function is meant to deal with. The underscore documentation for _.debounce() states:
Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed since the last time it was invoked. Useful for implementing behavior that should only happen after the input has stopped arriving.
Your refactored code would look as simple as:
setSearch: function(event) {
_.debounce(doSomething, 300);
},
Since you want your event handler events to be able to maintain whether or not an event has recentlyFired, you probably want to wrap your handler into a closure and maintain that status. The status should be changed to true when an event has fired, and reset to false after a delay of 500ms.
setSearch: function( ) {
var firedRecently = false;
return function(event) {
if (firedRecently) {
// it has fired recently. Do you want to do something here?
} else {
// not fired recently
firedRecently = true;
// run your function here
alert('fired');
var resetStatus = window.setTimeout( function () {
firedRecently = false;
}, 500);
}
}
}( );

How to rate-limit ajax requests?

There are several divs and handler to send ajax requests when they are clicked. My problem is that i don't know how to force my handler not to exceed limit of 1 request per 30 seconds.
Appreciate your help!
The excellent Underscore.js has a throttle function. You pass in the handler that you want to throttle and get back a rate-limited version of the same function.
var throttled = _.throttle(someHandler, 100);
$(div).click(throttled);
http://documentcloud.github.com/underscore/#throttle
Here's a simplified version that I've used in my own code:
function throttle(func, wait) {
var timeout;
return function() {
var context = this, args = arguments;
if (!timeout) {
// the first time the event fires, we setup a timer, which
// is used as a guard to block subsequent calls; once the
// timer's handler fires, we reset it and create a new one
timeout = setTimeout(function() {
timeout = null;
func.apply(context, args);
}, wait);
}
}
}
A good way to test it is by firing off a bunch of scroll events and watching your handler log to the Firebug console:
document.addEventListener("scroll", throttle(function() {
console.log("test");
}, 2000), false);
Here's a version that limits click-events on divs to once every 30 seconds, as requested (requires jQuery):
$("div").click(throttle(function() {
// ajax here
}, 30000));
If you want to rate limit, then unfortunately the _.throttle method that underscore.js provides is not your solution. Throttle will simply ensure your method is never called more than X seconds, and therefore all subsequent function calls will be disregarded until that period has passed.
If you want to rate limit so that you never call your function more than X times per second, but don't lose those function calls altogether, then you need a wholly different solution.
I have written an underscore extension at https://gist.github.com/1084831
You can see a working example at http://jsbin.com/upadif/8/edit#preview
Create a boolean canFireRequest, or whatever, flag and set it to false after each ajax request. Then create a 30 second time span that sets it back to true; check the flag's value before each new request.
Here's a rough example:
if ($(this).data('canFireRequest')) {
// Ajax request goes here
$(this).data('canFireRequest', false);
}
setTimeout(function() {
$(this).data('canFireRequest', true)
}, 30000);

Categories