JavaScript and the runaway timer
26 Jan 2012One of the things that you probably will run into when creating a JavaScript application is the runaway timer. The runaway timer? Yes, the runaway timer. Ever seen this message?
or this one?
That is the runaway timer telling you that something is running out of control. It is the thing that keeps your browser from crashing or not responding because of a long running script. JavaScript in browsers is executed in a single thread. But, any DOM manipulation or event handling is also done on this single thread (yes I’m ignoring web workers in this post). When your script runs for seconds, no other thing can be done thus making your browser unresponsive. Clicks on buttons or hyperlinks will not be executed, hovers or other effects will do nothing. You should be timing your JavaScript code and split everything that takes more than 100 ms. Why 100 ms? The different runaway timers have enforce limits of multiple seconds. Usability research however shows that humans find a User Interface responsive when it reacts within 100 ms.
I myself ran into problems with the runaway timer when implementing the Mandelbrot figure using JavaScript and the Canvas element. The basis of the mandelbrot figure is an algorithm that calculates a color for every pixel on the canvas. A nested for-loop that is, visiting every pixel and doing some calculation. With a 400 by 400 pixel canvas, I was already running into problems.
#js
var paint = function () {
for (var screenX = startpoint_x; screenX < endpoint_x ; screenX++) {
for (var screenY = startpoint_y; screenY < endpoint_y ; screenY++) {
var xynumber = getNumber(screenX, screenY, center_x, center_y);
var color = getColor(xynumber);
setColor(color);
canvascontext.fillRect(screenX + canvas_half_width, screenY + canvas_half_height, 1, 1);
}
}
};
The solution was to split this up in parts that run within the 100 ms limit (actually I choose 50 ms blocks to stay on the safe side).
#js
var paint = function () {
paintPart(startpoint_x);
};
var paintPart = function(start_x) {
var start = new Date();
for (var screenX = start_x; screenX < endpoint_x && (new Date() - start) < 50 ; screenX++) {
for (var screenY = startpoint_y; screenY < endpoint_y ; screenY++) {
var xynumber = number.getNumber(screenX, screenY, center_x, center_y);
var color = number.getColor(xynumber);
setColor(color);
canvascontext.fillRect(screenX + canvas_half_width, screenY + canvas_half_height, 1, 1);
}
}
if(screenX < endpoint_x) {
setTimeout(function() { paintPart(screenX); }, 25);
}
};
The effect of applying this pattern is that the browser stays responsive. Of course, the splitting also gives some overhead. But the site and the browser stay usable, and that is key in every good UI.
Want to more about creating responsive JavaScript applications? This presentation by Nicholas Zakas is a must see.