Is it a good idea to use requestAnimationFrame within a debounce function? - javascript

This is a check on my understanding of requestAnimationFrame. I have a need for a debounce function, as I'm doing some DOM interaction every time the window is resized and I don't want to overload the browser. A typical debounce function will only call the passed function once per interval; the interval is usually the second argument. I'm assuming that for a lot of UI work, the optimum interval is the shortest amount of time that doesn't overload the browser. It seems to me that that's exactly what requestAnimationFrame would do:
var debounce = function (func, execAsap) {
var timeout;
return function debounced () {
var obj = this, args = arguments;
function delayed () {
if (!execAsap)
func.apply(obj, args);
timeout = null;
};
if (timeout)
cancelAnimationFrame(timeout);
else if (execAsap)
func.apply(obj, args);
timeout = requestAnimationFrame(delayed);
};
}
The above code is a direct rip-off from the above debounce link, but with requestAnimationFrame used instead of setTimeout. In my understanding, this will queue up the passed-in function as soon as possible, but any calls coming in faster than the browser can handle will get dropped. This should produce the smoothest possible interaction. Am I on the right track? Or am I misunderstanding requestAnimationFrame?
(Of course this only works on modern browsers, but there are easy polyfills for requestAnimationFrame that just fall back to setTimeout.)

This will work.
It has a caveat that may or may not be important to you:
If the page is not currently visible, animations on that page can be throttled heavily so that they do not update often and thus consume little CPU power.
So if you for some reason care about this for the function you are debouncing, you are better off using setTimeout(fn, 0)
Otherwise if you are using this for animations, this is the intended usage of requestAnimationFrame

Related

jquery setTimeout too much recursion

I have read from multiple places that setTimeout() is preferable to setInterval() when setting something up to basically run forever. The code below works fine but after about an hour of running Firefox (38.0.1) throws an error of too much recursion.
Essentially I have it grabbing a very small amount of text from counts.php and updating a table with that information. The whole call and return takes about 50ms according to the inspectors. I'm trying to have it do this every x seconds as directed by t.
I suspect if I switch to setInterval() this would probably work, but I wasn't sure what the current state of the setTimeout() vs setInterval() mindset is as everything I've been finding is about 3-5 years old.
$(document).ready(function() {
t = 3000;
$.ajaxSetup({cache: false});
function countsTimer(t) {
setTimeout(function () {
$.getJSON("counts.php", function (r) {
$(".count").each(function(i,v) {
if ($(this).html() != r[i]) {
$(this).fadeOut(function () {
$(this)
.css("color", ($(this).html() < r[i]) ? "green" : "red")
.html(r[i])
.fadeIn()
.animate({color: '#585858'}, 10000);
})
};
});
t = $(".selected").html().slice(0,-1) * ($(".selected").html().slice(-1) == "s" ? 1000 : 60000);
countsTimer(t);
});
}, t);
};
countsTimer(t);
});
Update: This issue was resolved by adding the .stop(true, true) before the .fadeOut() animation. This issue only occurred in Firefox as testing in other browsers didn't cause any issues. I have marked the answer as correct in spite of it not being the solution in this particular case but rather it offers a good explanation in a more general sense.
You should indeed switch to setInterval() in this case. The problem with setInterval() is that you either have to keep a reference if you ever want to clear the timeout and in case the operation (possibly) takes longer to perform than the timeout itself the operation could be running twice.
For example if you have a function running every 1s using setInterval, however the function itself takes 2s to complete due to a slow XHR request, that function will be running twice at the same time at some point. This is often undesirable. By using setTimout and calling that at the end of the original function the function never overlaps and the timeout you set is always the time between two function calls.
However, in your case you have a long-running application it seems, because your function runs every 3 seconds, the function call stack will increase by one every three seconds. This cannot be avoided unless you break this recursion loop. For example, you could only do the request when receiving a browser event like click on the document and checking for the time.
(function()
{
var lastCheck = Date.now(), alreadyRunning = false;
document.addEventListener
(
"click",
function()
{
if(!alreadyRunning && Date.now() - lastCheck > 3000)
{
alreadyRunning = true;
/* Do your request here! */
//Code below should run after your request has finished
lastCheck = Date.now();
alreadyRunning = false;
}
}
)
}());
This doesn't have the drawback setInterval does, because you always check if the code is already running, however the check only runs when receiving a browser event. (Which is normally not a problem.) And this method causes a lot more boilerplate.
So if you're sure the XHR request won't take longer than 3s to complete, just use setInterval().
Edit: Answer above is wrong in some aspects
As pointed out in the comments, setTimeout() does indeed not increase the call stack size, since it returns before the function in the timeout is called. Also the function in the question does not contain any specific recursion. I'll keep this answer because part of the question are about setTimeout() vs setInterval(). However, the problem causing the recursion error will probably be in some other piece of code since there is not function calling itself, directly or indirectly, anywhere in the sample code.

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();
}
}
}

jsperf testing setInterval() vs requestAnimationFrame()

Can someone explain why turnEvenOld(250, 250)(0.089ms) runs much much more faster than turnEvent(250, 250)(0.447ms)?
I thought using requestAnimationFrame() was a lot faster and cheaper to run than setInterval()?
setInterval():
var turnEventOLD = function turnEvent(AnX, AnY) {
----VARIABLES----
temp = setInterval(myAnimation1, 1000/60);
function myAnimation1() {
----DRAWINGCANVAS------
-----
----CONDITIONS--------
if (one301 && one401) {
clearInterval(temp);
}
}
}
requestAnimationFrame():
var turnEvent = function turnEvent(AnX, AnY) {
----VARIABLES-----
function render() {
----DRAWING CANVAS-----
------
----CONDITIONS---------
if (one301 && one401) {
---stop requestAnimation--
}
}
(function animloop(){
----CONDTION-----
requestAnimationFrame(animloop);
render();
})();
}
RequestAnimation frame is not necessarily "faster" than setInterval. It actually does something different.
setInterval will wait a given number of milliseconds while requestAnimationFrame will wait until the page is ready to repaint. Depending on the time in your setInterval call, the time setInterval waits could be shorter or longer than the time until the next repaint.
It is better to use requestAnimationFrame for animations so you're sure you change the visual elements before the next repaint instead of being potentially out of synch with the page repaints.

Combined delayed function calls in Javascript

I am not quite sure what the technical term for this is. I have a GUI with interactive graphics. After the user has interacted with the GUI, I need to perform some CPU intensive action. However, user input is very frequent, so I only want to call the function after e.g. 1000ms of no userinput. Below the pattern that I use:
scheduler = (function(){
var timer;
function exec(call, delay){
clearTimeout(timer);
timer = setTimeout(call, delay);
};
return exec;
})()
I.e. if the 3 calls to scheduler are done right after each other, only the final one will actually be executed:
scheduler(function(){alert('foo')}, 1000);
scheduler(function(){alert('bar')}, 1000);
scheduler(function(){alert('zoo')}, 1000);
It seems to work, but it feels a bit hacky I am a little worried about any caveats of Javascript setTimeout, especially the scoping problems. Does this seem like a reliable pattern I could use on a larger scale? Will the inline function that I pass to scheduler be able to lookup all objects in its lexical scope as usual, when it is called by settimeout? What about if I have several of these scheduler instances? Could they interfere with each other? Is there an alternative way of accomplishing this?
You could opt for using web worker threads instead:
https://developer.mozilla.org/en-US/docs/DOM/Using_web_workers
http://www.html5rocks.com/en/tutorials/workers/basics/
What I would do:
http://jsfiddle.net/gunderson/4XXQ4/1/
var severQueue = [];
var delay;
$("#inputSquare").mousemove(onMouseMove);
function onMouseMove(){
if (delay){
clearTimeout(delay);
}
serverQueue.push("doSomething")
delay = setTimeout(sendToServer, 1000);
}
function sendToServer(){
console.log(serverQueue.length);
delay = null;
$("#inputSquare").addClass("activated");
// do some ajax using serverQueue
// we'll just simulate it with another timeout
for (var i in serverQueue){
serverQueue.pop();
}
onComplete = setTimeout(onAjaxComplete, 1000);
}
function onAjaxComplete(){
$("#inputSquare").removeClass("activated");
}
​
In theory, your solution looks like it will work. There are no scoping problems related to you passing a callback function to your scheduler function; the callback will close over whatever environment it was created in, just like any other function in JavaScript. That being said, scoping rules can be a bit tricky in JavaScript, so make sure that you read up on it.
In practice, there may be some browser-specific issues related to setTimeout that may make this solution unworkable. For example, the frequency at which certain browsers execute setTimeout callbacks may vary such that you'll be waiting longer than you expect for a callback to be executed. All setTimeout callbacks will be executed sequentially; they'll never be executed in parallel. However, you have guarantees as to what order they will be executed in.
All that being said, any major gotcha in your solution will likely have more to do with the callbacks that your registering rather than the way in which you're registering them.
The debounce function in underscore.js does exactly this:
debounce _.debounce(function, wait, [immediate])
Creates and returns a new debounced version of the passed function that 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.

Is there any way to execute some code when a thread is killed via clearInterval?

Is there anything like a "dispose" function or "thread shutdown hook" for a specific thread running via setInterval?
(function () {
var _setInterval = window.setInterval,
_clearInterval = window.clearInterval;
window.setInterval = function (fn, time) {
//Implement your hooks here, hopefully without side effects
return _setInterval(fn, time);
};
window.clearInterval = function (id) {
//Implement your hooks here, hopefully without side effects
return _clearInterval(id);
};
})()
From comments it became clear that you don't need hooking because you are in an environment that you control. In that case you can just write wrapper functions like myClearInterval etc. with same principle.
First off, as others have said, there are no threads in javascript (other than WebWorkers, but I don't think that's what you're talking about here.
All setInterval() does is call a function on a repeated basis - it isn't a thread, it isn't pre-emptive and it won't get called until all other javascript has stopped executing so the timer event can be processed. If your issue is that you want to dispose of some state when the interval is cleared so it will no longer be called, then you have two options:
1) You can use a javascript closure to store your state and when the interval is cleared, the closure will automatically be released.
2) You can create your own version of clearInterval that both clears the interval timer and cleans up your state.
The javascript closure option would look like this:
var interval;
function startMyInterval() {
// sample state variables
var state1 = 0;
var state2 = [];
var state3 = {whatever: "whatever"};
interval = setInterval(function() {
// javascript code here that can reference state1, state2 and state3
}, 1000);
}
// then some time later when you want to stop the interval, you call clearInterval
// the closure is released and all the state variables are freed automatically
clearInterval(interval);
Or, if you want to do any other things when the interval is cleared, you can make your own function for clearing the interval that will not only release the closure, but also let you run any other code.
function clearMyInterval() {
clearInterval(interval);
// do any other cleanup you want to when the interval is stopped
}
I see that others have suggested hooking/replacing window.clearInterval() with your own function, but I prefer not to do that because it is unclear if that is a supported/documented feature and some system functions (more and more of them over time) are becoming protected so they cannot be replaced.

Categories