JavaScript Timer-Based Pseudo-Threading

Contributing Editor

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:

  1. setTimeout and setInterval are passed a reference to a function (there’s no parenthesis). The code setTimeout(myfunction(), 500); would run myfunction() immediately.
  2. The millisecond timings will rarely be accurate. They only specify that a function should run when the browser becomes idle after a specific period.
  3. 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]);
note: func.shift()() ?!

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.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • kissmyawesome

    arguments.callee is deprecated, so technically you should use a named function instead:
    function ProcessThread(func) {
    var ms = 20;
    setTimeout(function threadTimer() {
    func.shift()();
    if (func) {
    setTimeout(threadTimer, ms);
    }
    }, ms);
    }
    ProcessThread([f1, f2, f3]);

    • http://dt.in.th/ the DtTvB

      Though named functions may look better under some circumstances, arguments.callee is still there. :)

      It is in fact arguments.caller which is deprecated.

      • kissmyawesome

        I was convinced it was .callee, but you’re right – thanks for correcting me.

  • http://www.brothercake.com/ James Edwards

    I wrote about this a few years ago; might interest you — http://www.sitepoint.com/article/multi-threading-javascript