Stepped Intervals in JavaScript
We’re adding some gmail-like JavaScript to DarwinGames and it’s been great to see the Ajax / JS communities share tips and techniques. My donation to JS-land is the following, but first a short story.
Let’s pretend you run a website and want to some check running in the background (maybe chat, maybe email or private messages, maybe stock ticker updates or whatever). As you probably know JavaScript provides two useful functions: setTimeout() (run function after $n ms) and setInterval() (run function every $n ms).
We could be satisfied with the defaults and check every minute or so for updates and pull a refresh, but it happens that in some cases we can make a much better guess about when and how to check. If the participants in the game happen to be online at the same time, it’s likely they’ll be making moves very quickly (less than 30 seconds in some games). If they aren’t online at the same time or are taking a long time to move, we really want to “back off” how frequently we’re checking (definitely not every 30 seconds).
Let’s start with an example:
document.steppedIntervalId = window.setInterval( steppedIntervals( new Array( new Array( 20, 6 ), new Array( 60, 5 ), new Array( 120, 30 ) ), targetFunction ), 1000 );
The stepped interval here is “20 seconds for 6 times (2 minutes)”, then “60 seconds for 5 times (5 minutes)”, then “2 minutes for 30 times (1 hour)”. Every valid “tick”, targetFunction is called and after all steppedIntervals have been accounted for, the checking stops. You should understand the array that’s passed into steppedIntervals, so now to explain how the rest of it works.
First understanding setInterval. setInterval takes two paramaters- a function to call and an interval (in milliseconds) at which to call it.
// function to call function foo() { alert( "hello world" ); } // ask the browser to call it for us timerId = window.setInterval( foo, 10 * 1000 );
What we’re doing with the steppedIntervals call is generating a function for setInterval to use. In the above example, I stick with 1s intervals since it makes the rest easier to understand at the expense of making the computer do a bit of extra work (as opposed to using 10s intervals).
steppedIntervals actually creates a function and returns that function to the caller. If you accept that a function is just as valid of a thing to return as an object or an int, then you can leave it at that — however I know a lot of people (myself included) haven’t worked in languages that allows you to create and return functions (C, C++, Java don’t really make it easy to do that).
I believe in hardcore computer geek terms this is called a “closure” … a function that generates or returns another function, and that other function can receive variables and maintain it’s own local state. Ruminating on that for a while makes the rest of it easy to understand… declare some local variables, declare some behaviour in a function and every time the function is called, do some bookkeeping to make sure we behave the way the caller requested.
function steppedIntervals( intervalsAndLoops, targetFunction ) { // local variables var localCounter = 0; var localLoopCounter = 0; var localEra = 0; var currentInterval = intervalsAndLoops[0][0]; var currentLoop = intervalsAndLoops[0][1]; return function() { localCounter ++; if ( localCounter % currentInterval == 0 ) { // interval triggered localLoopCounter ++; targetFunction(); if ( localLoopCounter > currentLoop ) { // era rollover localEra ++; localCounter = 0; localLoopCounter = 0; if ( localEra >= intervalsAndLoops.length ) { // ran out of eras window.clearTimeout( document.steppedIntervalId ); } else { // advance to next era currentInterval = intervalsAndLoops[ localEra ][0]; currentLoop = intervalsAndLoops[ localEra ][1]; } } } } }
…viola! Some ideas for expansion-
allow “-1” to indicate “don’t ever terminate” (useful for something that should settle into a steady state
figure out a way to reduce dependencies on “document.steppedIntervalId”. My lisp-fu isn’t good enough to figure out how to get the browser’s “interval ID” and then pass it back into the function that originated the interval in the first place.
21:18 CST | category / entries
permanent link | comments?