WorkerQueue: Background tasks in JavaScript

One of the most enjoyable aspects of maintaining Sheetnode is getting to practice more JavaScript. Douglas Crockford called it the World's Most Misunderstood Programming Language and that's certainly how it's been for me until I decided to put a concerted effort in finding out more about it.

Sheetnode's underlying spreadsheet engine, SocialCalc, is entirely client-side, JavaScript-based. One of the most challenging parts of the engine is the recalculation mechanism: when a cell value changes, we need to recalculate all the cells that depend on its value. This recalculation happens client-side, but it should not hang the UI. To solve this, the code uses the only "asynchronous" mechanism available, namely DOM's window.setTimeout and window.setInterval. John Resig has a great in-depth article on what these functions do (and how they differ).

What if you wanted to execute a sequence of JavaScript operations, like Batch API but on the client-side? You would have to write your functions such that each step calls setTimeout to schedule the next one. This would make for ugly, hard-to-follow code - not "literate programming" by any stretch! That's where this simple JavaScript class I wrote comes in: WorkerQueue lets you push functions to be executed in sequence. Because you can instantiate several queues, you can have several concurrent tasks running on your browser, without hogging your UI.

/**
* WorkerQueue.js
* Class to handle long-running, asynchronous tasks.
*/
WorkerQueue = function(frequency) {

  this.queue = [];
  this.timeout = 0;
  this.current = null;
  this.frequency = frequency;

  this.pop = function() {
    if (!this.current) {
      if (!this.queue.length) {
        window.clearInterval(this.timeout);
        this.timeout = 0;
        return;
      }
      this.current = this.queue.shift();
    }
    if (this.current()) {
      this.current = null;
    }
  }

  this.push = function(task) {
    var self = this;
    this.queue.push(task);
    if (!this.timeout) {
      this.timeout = window.setInterval(function(){ self.pop(); }, this.frequency);
    }
    this.pop();
  }
}

To use this, you need to instantiate your worker queues and fill them with functions that return true when they're done or false if they need to run again. Check github for a demo.

Comments

This very simple code was picked up a few days later and greatly expanded upon. Gotta love open source :-)