This is a re-write of an old post, written back in 2007. I recently re-used this code in another script and it’s as useful today as it was then. Plus I understand it a little better now, so I thought it was time to re-discuss it.


As any client-side web developer knows, DOM scripting is inherently slow. Accessing and manipulating the DOM requires the browser’s javascript engine to interface with the DOM API – which in of itself is slow: any time you have two fundamentally different components talking to one another via an interface you’re going to get a bit of a bottleneck. But also, accessing and especially changing the contents of the DOM can cause reflows (the re-creation of the internal representation of the visible page within the browser) and repaints (the actual re-rendering of the visible web page to the user). And this is slow!

So what of it?

Well, here’s a common scenario. Try dynamically inserting a large block of HTML into your page, then immediately accessing an element within it. Does it work? Well… sometimes it may, sometimes it may not – it can depend on the quality of the browser, how busy the CPU is, the OS, the volume of HTML inserted – a whole range of factors. From a coding standpoint (and this is what *I* find interesting!), it looks fine: on one line you insert the content, on the next you access it. What could be wrong with that? Well, this is one of those baaaad instances where you need to have an understanding of what’s going on within the browser. You shouldn’t, but you do.

So here’s how to get around it. The following code lets you queue your DOM changes. It only proceeds to the next item in the queue when a custom assertion (e.g. “does element X exist?”) is confirmed. This can ensure that regardless of browser, environment, etc. your code will execute in a sequential manner where in every step you can rely on the DOM containing the content that you expect.

The code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var $Q = {
    // each index should be an array with two indexes, both functions:
    // 0: the code to execute
    // 1: boolean test to determine completion
    queue: [],
    run: function()
    {
        if (!$Q.queue.length)
            return;

        // if this code hasn't begun being executed, start 'er up
        if (!$Q.queue[0][2])
        {
            $Q.queue[0][0]();
            $Q.queue[0][2] = window.setInterval("$Q.process()", 50);
        }
    },
    process: function()
    {
        if ($Q.queue[0][1]())
        {
            window.clearInterval($Q.queue[0][2]);
            $Q.queue.shift();
            $Q.run();
        }
    }
}

Usage

It works very simply. Any time you have something that could take up lots of time, just add it to the queue. When you’re ready to run that code, just run() it.

1
2
3
4
5
6
7
8
9
10
11
12
$Q.push([
  function() {
    // do expensive stuff here...
  },
  function () {
    // do our test here and stash it in $boolean
    ...
    return $boolean;
  }
]);

$Q.run();