Search This Blog

2011-10-31

jQuery Deferred queue

Since jQuery 1.5 had been released some very good feature called Deferred Object appeared for better callbacks handling and other kinds of synchronizing calls.
Deferred API has "jQuery.when()" which allows to multiplex deferreds (waiting on multiple deferreds at the same time)... and has "pipe()" to create a pipeline or chain of deferreds (since version 1.6). While this is very nice if all deferreds are available at the same point, it doesn't really convenient if there is a collections of deferreds coming from multiple sources (which may not be aware of one another). This plugin allows to solve the problem at some point. But it was based on previous version... before pipe() feature.
My simple solution also tries to handle multiplexing deferreds from different sources using shared Queue object.
Idea is to have a queue that is based on the principle of FIFO multiple-producers multiple-consumers tasks queues.
Let's start from unit test to describe how it works:
module("Test.Queue.js");

asyncTest("Test of appending to the queue ", 6, function () {
  var queue = new Queue();
  queue.append(function () {
   ok("func 1");

   setTimeout(function () {
      ok("func 1 nested");
      start(); // Continue async test. 
      this.resolve(1); // return some value for the subscribers.
    }.bind(this), 500); // Here I would like to have a control over Deferred through 'this'.
  })
  .done(function (arg) { // on done callback
    ok("func 1 done callback");
    equal(arg, 1);
  });

  queue.append(function (arg) {
    equal(arg, "test arg data");
    this.reject(); // operation was failed.
  }, "test arg data")
  .fail(function (funcArg) { // on fail callback
    ok("func 2 fail callback");
  });
});
So here we have a queue object and two calls of independent functions that might come from the multiple sources. Each function itself might have a callbacks or chain so "queue.append" should return a promise.
The assert trace should look like this: "func 1", "func 1 nested", "func 1 done callback", "func 2" and "func 2 fail callback".
And the code itself:
Queue = function () {
  this.promise = jQuery(this).promise(); // Stub promise
};

// Magic with arguments is needed to pass arguments to the called function
Queue.prototype.append = function () {
  var args = arguments;

  var fn = args[0];
  if (!fn || !jQuery.isFunction(fn)) {
    throw new TypeError('1st parameter should be a function');
  }

  var self = this;
  args = Array.prototype.slice.call(args, 1);
  return this.promise = this.promise.pipe(function () {
    return jQuery.Deferred(function () {
      try {
        return fn.apply(this, args);
      } catch (ex) {
        // log exception
        this.reject(ex);
        return self.promise = jQuery(self).promise();
      }
    }).promise();
  });
};
Implementation idea is very simple: Class just has a reference to the head of a queue and push new function to the Deferred pipeline.

1 comment:

  1. Nice post. If an error occurs the whole queue execution is halted.

    ReplyDelete