[EDIT: Dec, 2012] I once again revisited this problem with a recent project and rewrote the code below into a (much more advanced) requireJS module, adding things like onSuccess and onError event handling for the Queue. You can find the code here. I’ll be using it in the Data Generator 3.0.0 rewrite [https://github.com/benkeen/generatedata].
If several hundred forum posts are anything to go by, people generally rely on the trusty setTimeout function to get around this problem, but every time I did this it really stuck in my craw. This was for two reasons:
- Using a timeout involves guesswork: are you sure that timeout’s long enough to ensure the code can now safely execute? How about those cases where people’s browsers are busy doing other things, or are just plain slow?
Well, to answer #2 for similarly bewildered people: statements DO only get executed after the previous statement has been completed. The confusion lies in the fact that all modern browsers implement innerHTML by converting the raw string into DOM nodes after it’s been inserted – and this takes time. So while the initial statement of setting the innerHTML has been completed, the content is still not accessible for a short period of time.
I recently encountered a situation that no judicial use of timeouts could solve. The problem was with the load mechanism for my Data Generator script. When a user is logged in and wishes to load a saved form, it uses Ajax to pull from the server the entire form content stored in a JSON object, then populate the form, row by row. This can potentially take large amounts of processing time – even several seconds, depending on the size of the form. In this situation, there were so many page elements being shifted around, it was simply too complex to get right with multiple setTimeouts.
An aside about innerHTML vs. DOM / A disclaimer for why I used innerHTML
Building and manipulation DOM nodes is elegant, controlled and standardized. Reason enough to take that approach, you’d think. But I do believe some situations call for innerHTML. Here’s a few things to keep in mind:
- innerHTML is faster than DOM manipulation, both for processing time and development time,
- innerHTML is simpler to use and therefore less prone to errors than DOM manipulation,
- using innerHTML over the DOM can result in (potentially significantly) less JS code to be downloaded by the client browser,
- innerHTML contents after insertion are, on all major browsers, converted to DOM nodes and therefore normally accessible through JS (eventually…),
- innerHTML, even though non-standard, is implemented on all major browsers (albeit with minor, frustrating differences).
Okay, back on topic.
 : code to execute - (function)
 : boolean test to determine completion - (function)
 : interval ID (managed internally by script) - (integer)
var g_queue = new Array();
// if this code hasn't begun being executed, start 'er up
// run the code
timeout_id = window.setInterval("check_queue_item_complete()", 50);
g_queue = timeout_id;
There’s a single global array: g_queue which stores the actions to queue. The first two elements are function objects. I chose that particular data structure because it provided an elegant way to pass in groups of statements, not just a single line of code, which wasn’t sufficient in my case.
The overall process is pretty straightforward: it executes the content of the first index ONCE, then puts the boolean test (the second index) in a setInterval call, to be executed however often you want (here, it’s set to 50 milliseconds). Only when the boolean test returns true does it remove the entry from the queue and start processing the next code in line. If there’s nothing in the queue, it just returns.
Here’s how you’d add an item to the queue.
// here's the code that takes TIME (e.g. innerHTML statements). These statements only get executed once.
// this is our boolean test (e.g. if ID inside the innerHTMLed content exists).
// It would return TRUE when the code in the previous function has been fully executed.
Then, once you’ve added one or more items, just call the process_queue() function to sequentially execute the statements. And bingo! “True” sequential processing even for innerHTML calls.
I very much hope this solution comes in handy!