Fun with UI blocking and progress bars

I recently stumbled across an interesting problem, which I can’t find many people asking questions about. I’ve been working on some visualisations to do with New Zealand’s electricity spot price market, which rely heavily on dc.js and crossfilter. That Generation tab you see has a lot of data behind it, and it takes a good few seconds to load.

The obvious thing to do was to show a sensible progress bar, so users could see that something was in fact happening. NProgress fit the bill very nicely – except that the various tasks involved in setting up crossfilter and building the charts block the UI thread, preventing any progress bar updates from actually taking effect.

This (in the general sense) is a common problem, and the common solution seems to be to use

window.setTimeout(myCode, 0);

to asynchronously queue the code up to be called after a zero millisecond delay (which means, after the stack is empty). This works great, but what if you have more than one thing you need to do?

I actually couldn’t find much in the way of people talking about this, which probably means that either there’s an incredibly obvious solution I don’t know about, or nobody is trying to show a progress bar for dc.js… After all, we do try to avoid big computations in the browser under usual circumstances. After you’ve read this, let me know if you’ve dealt with something similar!

Anyway, things are executed in the right order if you have a sequence of these calls, in Chrome at least:

window.setTimeout(function() { console.log("Hello"); }, 0);
window.setTimeout(function() { console.log("Hello again"); }, 0);
window.setTimeout(function() { console.log("Goodbye"); }, 0);

But the UI thread is still blocked! I think this is because the stack is never free for UI things to do their business until after all three of these calls have completed.

So, then, it seems that one must nest these calls:

window.setTimeout(function() {
console.log("Hello");
window.setTimeout(function() {
console.log("Hello again");
window.setTimeout(function() {
console.log("Goodbye")
}, 0);
}, 0);
}, 0);

Yuck! After verifying that this did in fact solve my problem, I thought it was time to encapsulate the technique into something a bit more convenient. So I wrote a function called LoadChain:

function LoadChain() {
  this.queue = [];

  this.push = function(f) {
    var self = this;

    // Push a function onto the queue that executes the user-specified function, then sets a timeout to execute the
    // next function if there is one
    this.queue.push(function() {
      f();
      var next = self.queue.shift();
      if(next) window.setTimeout(next, 0);
    });
  };

  this.start = function() {
    var first = this.queue.shift();
    first();
  };
}

Now my code can look like this:

var loadChain = new LoadChain();
loadChain.push(function() { console.log("Hello"); });
loadChain.push(function() { console.log("Hello again"); });
loadChain.push(function() { console.log("Goodbye"); });
loadChain.start();

This is basically what I did in my aforementioned app, and it works well – now I can increment the progress bar when the data is loaded, when dates have been parsed, when crossfilter has been set up, after creating each dimension, after creating each chart, etc and have a good indication of the actual loading progress – invaluable on a slower machine where there might be a several second wait.

Have you solved this problem a different way? I’m interested to hear any thoughts.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s