HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Alternative to setInterval and setTimeout

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
setintervalandalternativesettimeout

Problem

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

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

-
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

-
Normally I don't write functions inside functions, but in this case it's probably a go

Solution

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.

Code Snippets

function tick(){
  requestAnimationFrame(tick);

  // Oh...
  var aFunctionMadeOnEachTick = function(){...};
  aFunctionMadeOnEachTick();
}
;(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 || {}));

Context

StackExchange Code Review Q#47889, answer score: 6

Revisions (0)

No revisions yet.