FuturesJS - Promises, Subscriptions, Joins, Synchronizations, etc

What is FuturesJS?

FuturesJS is a JavaScript library which (when used as directed) simplifies handling Callbacks, Errbacks, Promises, Subscriptions, Joins, Synchronization of asynchronous data, and Eventually Consistent data. It is akin to this well documented this MSDN library, but with a liberal MIT license.

Downloads

Development Version (0.5.2) 15kb, Uncompressed with Comments

Try it now in your browser

Just open up your favorite JavaScript console and play

Updated July 31th 2010.

Promises, Immediates, and Joins

What is a Promise?

A promise (as per Douglas Crockford) is (essentially) a chainable callback object with the following methods:

Chainable means that the object returns itself so these methods can be called in sequence. A normal callback function might look like this:

var xhr = getAsyncData(arg1, arg2, callback, {onError : errback, setTimeout : 5000});

To use a promise with such a function you would do something like this:

var p = Futures.promise(),
xhr = getAsyncData(arg1, arg2, p.fulfill, {onError : p.fail, setTimeout : 5000});
p.when(callback1)
 .when(callback2)
 .when(callback3)
 .fail(callback4);

What are Immediates (Guarantees)?

Immediates (guarantees) are promises which are guaranteed to be fulfilled because they are immediately fulfilled. FuturesJS provides a shortcut method for just this case.

var p = promise('pass in the data');

Or if you prefer the "long" way:

var p = promise().fulfill('pass in the data').passable();

How do I Promisify my existing functions?

For simple cases you can just use "promisify":

// this is the quick'n'dirty convenience method
var myFunc = function (url, data, callback, errback) {
                    //  0,    1,      2,      3
                    //  let promisify know the index
};
myFunc = Futures.promisify(myFunc, { "when": 2, "fail": 3 });
myFunc(url, data) // now promisified
  .when(callback)
  .fail(errback);

TODO allow a nested map of attributes as well something like

["true", "optional", "when", {"timeout":"fail", "error":"fail"}]

But if you're a little more do-it-yourself-y, you can promisify a function in a number of ways:

A) return an object with the when() and fail() methods

function getAsyncData(param1, param2) {
    var p = Futures.promise(),
    result = oldGetAsyncData(arg1, arg2, p.fulfill, {onError : p.smash, setTimeout : 5000});
    // Implements a synchronous callback to hand back the original data
    p.withResult = function(func) {
        func(result); // XMLHTTPRequest object is the result in this case
    }
    return p;
}
var xhr;
getAsyncData(param1, param2)
    .withResult(function (r) {
        xhr = r;
    })
    .when(doStuff)
    .when(doMoreStuff)
    .fail(undoStuff);
if (i_change_my_mind) {
    xhr.abort();
}

B) allow a promise to be passed instead of a callback or returned with a synchronous callback.

function getAsyncData(param1, param2, p) {
    return oldGetAsyncData(arg1, arg2, p.fulfill, {onError : p.smash, setTimeout : 5000});
}
var promise = Futures.promise(),
xhr;
xhr = getAsyncData(param1, param2, promise);
promise
    .when(doStuff)
    .when(doMoreStuff)
    .fail(undoStuff);
if (i_change_my_mind) {
    xhr.abort();
}

C) pass back a promise as a synchronous callback

function getAsyncData(param1, param2, promiseback) {
    var p = Futures.promise();
    return oldGetAsyncData(arg1, arg2, p.fulfill, {onError : p.smash, setTimeout : 5000});
}
var promise,
xhr;
xhr = getAsyncData(param1, param2, function (p) {
    promise = p;
});
promise
    .when(doStuff)
    .when(doMoreStuff)
    .fail(undoStuff);
if (i_change_my_mind) {
    xhr.abort();
}

Where many functions allow only one callback, a promise allows you to enlist multiple callbacks before and after the target function has been called. FuturesJS provides convenience functions for you to wrap existing functions via promisify() (method A) and noConflict() (method C).

What are Joins?

A join is a special type of promise which allows you to get the results of multiple promises once all of them have completed or failed.

// Futures.join(p1, p2, p3, ..., pN)
Futures.join(p1, p2, p3) // There is no limit to how many promises you pass in
    .when(function (p_arr) {
        // p_arr[0] is the result of p1
        // p_arr[N-1] is the result of pN
    })
    .fail(function (p_arr) {
        // at least one of the promises was smashed
        // you'll have to discover which on your own
    });

TODO: allow a join to also accept a subscription In writing jaysyncunit I realized that this is useful to do

Recap: Why is this useful?

I have two main use cases:

If you're lucky I'll write some psuedo-code for these examples in Part 2.

Hang around and I'll explain more about subscriptions and other fun stuff.

Updated July 5th 2010.

Subscrptions, Triggers, and Synchroniztions

Last time I explained promises and hopefully you saw how they can be instrumental for creating more dynamic ajax-ical magic while avoiding some of the lexical ugliness that often occurs with callback chaining.

What is a subscription?

Whereas promises may only be fulfill()ed once, subscriptions may be deliver()ed any number of times. FuturesJS implements the following methods for a subscription:

Subscriptions are very similar to promises; here's a quick look:

var myFuturific;

(function () {
  var s = Futures.subscription();
  myFuturific = function (arg1, arg2) {
    var p = Futures.promise();
    var xhr = getAsyncData(arg1, arg2, s.deliver, {onError : s.hold, setTimeout : 5000});
    s.subscribe(p.fulfill);
    s.hold(p.smash);
    return p.passable();
  };
  myFuturific.subscribe = s.subscribe;
}());

// the same subscription is used for all calls of the function
var unsubscribe = myFuturific.subscribe(callback1)
myFuturific.hold(errback1);

// a new promise belongs to each instance
myFuturific()
  .when(callback2)
  .fail(errback2);

Notice that the same subsciption will fire each time the function is called and that and the promises are only for specific instance:

Triggers anyone?

Words like trigger, fire, and notify are being thrown all over the place these days. If you're not familiar with those terms... I don't know why you're reading this blog... but just in case you're a little behind the times; JavaScript is event-driven and that means that instead of polling (yuck!) to know what's going on in your application you can listen.

For the DOM, jQuery's delegate() is my favorite way handle events (I would also recommend looking into JavaScriptMVC's controller module).

The subscriptions in FuturesJS work decently as a poor-man's event trigger system - just deliver() without actually passing any data and she-bam, you've got an event trigger.

// Anonymous triggers - Ready, Aim... Fire!
// TODO - commit this change to the repo on github
Futures.trigger = function(ignore) {
  var s = subscription();
  return {
    listen: function(func) {
      return s.subscribe(func); // returns `unsubscribe()`
    }
    fire: function(ignore) {
      s.deliver();
      return this;
    },
  }
}
var t = Futures.trigger();
t.listen(func1).listen(func2);
t.fire();

// TODO why not throw in named triggers with messages too?

How do I Subscribify an existing function?

"There's a func for that"(TM).

Futures.subscribify(func, directive) is a hybrid of Futures.promisify(func, directive) and the example given at the top of this page.

var subscribable = Futures.subscribify(myFunc, { "when": 2, "fail": 3 });

NOTE: It isn't necessary to specify the directive if myFunc has the methods .when() and .fail()

// Pass in a duck-typed promisable
var subscribable = Futures.subscribify(myFunc);

Of course, you can still work your own fancy black magic too. For that I'll refer you to the example above and Part 1 (where I talk about remember good ol' methods A, B, and C).

Drop-in subscribables with noConflict()

You can can transparently subscribify a function which accepts the same parameters and returns the same results by calling noConflict(syncback) on the result.

Let's consider $.getJSON() from jQuery as an example:

var subscription;
$.getJSON = Futures.subscribify($.getJSON, {"when":2}).noConflict(function (s) {
  subscription = s; // This is a synchronous callback
});
var unsubscribe = subscription.subscribe(func1);
subscription..when(one_time_func);

var xhr = $.getJSON(url, data);

IN PROGRESS: I'm working on an interceptor that can handle hash maps, such as $.ajax

What are synchronizations?

Whereas joins fire only once, synchronizations trigger each time the subscribees make a full round of deliveries. If one subscription delivers multiple times before the others deliver once, only the most recent delivery will be used.

var z,
  unsubscribe;

z = Futures.synchronize(s1, s2, s3);
unsubscribe = z.subscribe(func);

TODO: only the most recent successful delivery should be used

TODO: allow a join to also accept a subscription

TODO: handle misses better

Updated July 8th 2010.

TODO - forthcoming article

Why assignment is bad

Use case for Futures

cache, callback, right promise at the right time, eventual consitency

What is Eventual Consistency?

Updated .