16
\$\begingroup\$

JavaScript's setTimeout() and setInterval() are evil and not precise:

  1. Both functions have a delay of a varying quantity of milliseconds.

  2. Both functions are very resource-intensive because they execute several times every second.

A new alternative is window.requestAnimationFrame(), which is less resource-intensive, disabled on page blur, and does not slow down other stuff. This makes it the perfect substitute for a modern setTimeout() and setInterval().

Description

These functions use requestAnimationframe() to check if the time has passed based on the elapsed Time calculated from Date.now(). The time passed is more precise than the native functions and theoretically less resource-intensive. Another advantage (or disadvantage) is that the functions are not executed on page blur.

Good for: animations, visual effects

Bad for: timers, clock

RafTimeout

window.rtimeOut=function(callback,delay){
 var dateNow=Date.now,
     requestAnimation=window.requestAnimationFrame,
     start=dateNow(),
     stop,
     timeoutFunc=function(){
      dateNow()-start<delay?stop||requestAnimation(timeoutFunc):callback()
     };
 requestAnimation(timeoutFunc);
 return{
  clear:function(){stop=1}
 }
}

RafInterval

window.rInterval=function(callback,delay){
 var dateNow=Date.now,
     requestAnimation=window.requestAnimationFrame,
     start=dateNow(),
     stop,
     intervalFunc=function(){
      dateNow()-start<delay||(start+=delay,callback());
      stop||requestAnimation(intervalFunc)
     }
 requestAnimation(intervalFunc);
 return{
  clear:function(){stop=1}
 }
}

Usage

var interval1,timeout1;
window.onload=function(){
 interval1=window.rInterval(function(){console.log('interval1')},2000);
 timeout1=window.rtimeOut(function(){console.log('timeout1')},5000);
}

/* to clear
interval1.clear();
timeout1.clear();
*/

Demo

jsFiddle

Questions

  1. Normally I don't write functions inside functions, but in this case it's probably a good solution. What about memory leaks if I create hundreds of these time-based functions?

  2. Is there a better solution to clear those functions?

  3. For heavy animations and multiple intervals and timeouts, I was thinking to activate a single requestAnimationFrame() loop which would check for intervals and timeouts inside a previously stored array... (but I think there should be no difference if there is just one requestAnimationframe or multiple). So how do the browsers handle those multiple requestAnimationframes()?

Note:

If the code above does not work here is the original code:

window.rInterval=function(a,b){var c=Date.now,d=window.requestAnimationFrame,e=c(),f,g=function(){c()-e<b||(e+=b,a());f||d(g)};d(g);return{clear:function(){f=1}}}//callback,delay
window.rtimeOut=function(a,b){var c=Date.now,d=window.requestAnimationFrame,e=c(),f,g=function(){c()-e<b?f||d(g):a()};d(g);return{clear:function(){f=1}}}
\$\endgroup\$
10
  • 4
    \$\begingroup\$ requestAnimationFrame is called on average 60 times per second.. At first sight, your code is far eviler. \$\endgroup\$
    – konijn
    Commented Apr 22, 2014 at 15:09
  • \$\begingroup\$ oh konijn, i see you like my posts ;), but i never get the sense of your answers. I know that requestAnimationframe is executed 60 times per seconds. But setTimeout checks more often if the delay is passed.. anyway .. explain yourself. Why is my code evil? \$\endgroup\$
    – cocco
    Commented Apr 22, 2014 at 15:14
  • \$\begingroup\$ Do you have a link that says that setTimeout checks more often, that is the first I hear of that \$\endgroup\$
    – konijn
    Commented Apr 22, 2014 at 15:16
  • \$\begingroup\$ simple setInterval(func,10) <- works... this is faster than 60fps 60HZ ... 60fps == 17ms \$\endgroup\$
    – cocco
    Commented Apr 22, 2014 at 15:23
  • \$\begingroup\$ As far as I can read your code, if I want to wait 170 ms, then your code will have executed requestAnimation 10 times, it would have executed setTimeout once, right ? \$\endgroup\$
    – konijn
    Commented Apr 22, 2014 at 15:28

1 Answer 1

6
\$\begingroup\$

Update 4/23/2014:

Okay then, off to your updated code. Nothing much I can do here now. Here are some generic code optimization tips:

  • Name stuff verbosely. It's one issue I find with others' code. They tend to name it like t for time, or l for length. Unless it's something implied, like i,j and k for counters (and we do see loops), please name them verbosely.

  • Don't ever worry about long code. That's what minifiers are for. Name them verbosely, structure them properly. Don't even bother using the "comma-separated vars". Minifiers do that.

  • Ternaries tend to be messy and unreadable. If possible, use if-else. Again, minifiers also convert them to ternaries to be shorter.

  • As for possible bottlenecks, splice is an overhead because it resizes the array, shifting the contents around. It's much avoided like unshift and shift for the same reason. I don't know if keeping a "hole" in the array is a performance benefit either, so let's leave it as is.

  • A general performance tip is just to avoid creating objects inside the loops. If it can't be avoided, just make sure you free them up when done (free references).

So far, that's about it. I'll leave the fine-tuning to you.

Previous answer:

Normally i don't write functions inside functions, but in this case it's prolly a good solution. What about memory leaks if i create hundreds of this timebased functions?

That wouldn't be an issue. Besides, how many functions do you think the FB homepage, or any site for that matter, creates? What you should be wary about, though, is when your timers create functions on every iteration. That's what you should look out for. Along the lines of this:

function tick(){
  requestAnimationFrame(tick);

  // Oh...
  var aFunctionMadeOnEachTick = function(){...};
  aFunctionMadeOnEachTick();
}

Although some JS engines are smart enough to figure this out and pull out the function, I have seen performance decreases when doing this in some of my projects.

Is there a better solution to clear those functions?

I was thinking about something along the lines of a common function that can be referenced by each API function to generate a runner. This way, we have one function sitting internally waiting to be called, instead of one created each call.

;(function (ns) {
  
  // An array of references/ids/booleans/whatever that can be used to clear off running timers
  var timers = [];

  // The common function
  // Haven't figured out how the cb and delay gets called on the next tick, but
  // this should be a good template to think about.
  function runningFn(cb,delay) {
    if(iShouldStillRun) requestAnimationFrame(runningFn);
    if(Date.now() > something) execute();
  }

  // Your API functions
  ns.setTimeOut    = function (callback, delay) {
    runningFn(callback,delay);
  }
  ns.setInterval   = function (callback, delay) {...}
  ns.clearInterval = function (callback, delay) {...}
  ns.clearTimeout  = function (callback, delay) {...}

}(this.Timers = this.Timers || {}));

For heavy animations and multiple intervals,timeouts i was thinking to activate a single requestAnimationFrame loop which check for intervals and timeouts inside a previously stored array...

The last time I checked, looping through an array with 700+ items caused stuttering in the UI. Also, by the nature of timers, they can actually be delayed by these operations. Again, JS is single-threaded, and timers also live in that thread. If JS is busy churning, it can delay async tasks and that includes timers.

rAF at a glance

One thing about rAF is that it's built for animation. It strives to give you a 60fps. However, when it can't, it isn't any different from setInterval and setTimeout. Also, it depends on browser implementation. Some browsers throttle timers according to different situations, like unfocused windows/tabs.

Let's start cherry-picking

Now here are some interesting things about your implementation:

  • You have an internal function in each instance of a timer. Looks like the root of your worries.
  • But you also return an object for each instance for the clear. Why not store some sort of reference to running timers, and create another global function that clears them?
  • Like you found out, 60fps is 16ms per draw. However, timers are as fast as 4ms.
\$\endgroup\$
11
  • \$\begingroup\$ yeah i knew it :(... i was just trying to keep one global variable per function. \$\endgroup\$
    – cocco
    Commented Apr 22, 2014 at 15:26
  • \$\begingroup\$ so basically i should define those 2 functions (intervalFunc,clear) outside and pass it as reference... \$\endgroup\$
    – cocco
    Commented Apr 22, 2014 at 15:28
  • \$\begingroup\$ @cocco If you wanted to avoid globals, then you could create a single global namespace for your functions. There, you can define 2 timer, and 2 clearer functions. \$\endgroup\$
    – Joseph
    Commented Apr 22, 2014 at 15:29
  • \$\begingroup\$ mh... yeah thats a problem... i didn't want to use a namespace.. in that case everything changes...then the 3rd question is the most appropriate.. create a namespace wich contains the functions and an array of active intervals and timeouts and a single requestAnimationframe.right? \$\endgroup\$
    – cocco
    Commented Apr 22, 2014 at 15:36
  • 1
    \$\begingroup\$ and yeah i'm using rAF exactly because of those 60fps vs much more.else the same function can be writtten with setTimout \$\endgroup\$
    – cocco
    Commented Apr 22, 2014 at 15:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.