JavaScript Timer-Based Pseudo-Threading
In my previous post, JavaScript Execution and Browser Limits, I described how the top 5 browsers determine when JavaScript code has run for too long and throw “unresponsive script” errors. It’s not practical to change the browser’s behavior and it may not always be possible to offload processing to the server. Fortunately, timers can help us execute long-running tasks without locking the browser.
What are timers?
JavaScript code, such as an individual function, can be set to run after a particular period of time has elapsed:
- setTimeout(function, msec[, arg1 … argN])
runs the named function after msec milliseconds have passed. The arguments, if any, are passed to that function. - setInterval(function, msec[, arg1 … argN])
is similar to setTimeout except the function is called indefinitely every msec milliseconds.
Two other functions, clearTimeout()
and clearInterval()
, will cancel timer operations, e.g.
var timerID = setTimeout(myfunction, 500);
clearTimeout(timerID); // myfunction() will never be called
Note:
- setTimeout and setInterval are passed a reference to a function (there’s no parenthesis). The code
setTimeout(myfunction(), 500);
would run myfunction() immediately. - The millisecond timings will rarely be accurate. They only specify that a function should run when the browser becomes idle after a specific period.
- Don’t rely on functions running in a specific order, e.g.
setTimeout(f1, 50); setTimeout(f2, 50);
— f2() could execute first.
Timer-based execution
Timed code isn’t run immediately, so the browser processing thread is released to perform other tasks. We can therefore split long processes into shorter chunks.
As a simple example, assume we want to run the functions f1(), f2() and f3() in order:
function ProcessThread(func) {
var ms = 20;
setTimeout(function() {
func.shift()();
if (func) {
setTimeout(arguments.callee, ms);
}
}, ms);
}
ProcessThread([f1, f2, f3]);
That requires a little further explanation; func.shift() removes the first item from an array and returns it. That will be reference to a function, so we add parenthesis to execute it.
The statement is functionally identical to var f = func.shift(); f();
ProcessThread runs all the functions in the passed array, but waits 20ms between each. Any number of functions can be executed in sequence … assuming no individual function throws an “unresponsive script” error.
However, the most time-intensive code will probably be processing large arrays of data. In my next post, we’ll write more robust code to handle that type of operation.