In this post, I am going to look at the similarities between jQuery Deferred and Microsoft Reactive Extensions, and supply code which will convert between the two. Although Deferred and Rx have vast differences, there are some similarities which one may wish to take advantage of. For those who have no prior knowledge of the two technologies, I’ll give a quick introduction to the basics.
jQuery Deferred
The jQuery Deferred
concept is a pretty simple one. When you want to perform an asynchronous operation, such as an Ajax request or an animation, you can get a synchronous response which is a Deferred object. This object simply represents the state of that asynchronous operation.
What does one do with this Deferred
object? Well, a few things:
- Register for a notification when the asynchronous operation has completed (the
Deferred
object is “resolved”) - Combine a number of
Deferred
objects into a single Deferred
object - Perform a specific action when the completion notification is received
- Do something special when an error occurs during the asynchronous operation
A typical Deferred
code snippet might look like the following, where we do something when an Ajax request is complete:
var deferred = $.ajax("hello.html");
deferred.complete(function(result){
});
Note that this only applies to jQuery 1.5 – it is the first version to include Deferred
, and the Ajax function has been modified to return a Deferred
object.
You can also create a custom Deferred
object for something asynchronous, like for example an animation:
function fadeIn(selector) {
var deferred = $.Deferred();
$(selector).fadeIn( 1000, deferred.resolve );
return deferred.promise();
}
Microsoft Rx
Reactive Extensions is a much larger library and brings with it a whole new way of programming – “reactive” programming. This revolves around the idea of having an Observable source of items, as opposed to an Enumerable
source. Therefore, we react to a new item in the collection instead of requesting one – the Observable
is a ‘push’ collection as opposed to the traditional Enumerable
‘pull’ collection. I could sum up the key features as follows:
- Ability to listen to a source of events (an
Observable
) – these could be, but aren’t restricted to, actual events - Ability to filter that
Observable
using a LINQ-like syntax - Ability to modify the items in the
Observable
, e.g., select a particular attribute of an event parameter - Combine a number of sources into a single source
- Subscribe to the
Observable
source and react when an item is received - Handle errors as they occur during the above processing
Below is a sample piece of Rx code. This listens to three events – mouse down, mouse move and mouse up, combining them using the LINQ-like selectors to produce a “composite” stream of events – one which represents mouse moves that occur between a mouse down and mouse up. We then react to those by drawing a simple line.
var canvas = document.getElementById("rxCanvas");
var mouseMoveEvent = Rx.Observable.FromHtmlEvent(canvas, "mousemove");
var mouseLeftButtonDown = Rx.Observable.FromHtmlEvent(canvas, "mousedown");
var mouseLeftButtonUp = Rx.Observable.FromHtmlEvent(document.body, "mouseup");
var draggingEvents = mouseMoveEvent.SkipUntil(mouseLeftButtonDown)
.TakeUntil(mouseLeftButtonUp)
.Let(function(mm) {
return mm.Zip(mm.Skip(1), function(prev, cur) {
return {
X2: cur.offsetX,
X1: prev.offsetX,
Y2: cur.offsetY,
Y1: prev.offsetY
};
})
})
.Repeat();
var context = canvas.getContext("2d");
draggingEvents.Subscribe(function(p){
context.moveTo(p.X1,p.Y1);
context.lineTo(p.X2,p.Y2);
context.stroke();
});
Here is that powerful piece of Rx in action. Sorry Internet Explorer users – this sample uses canvas! Also, I should rightly attribute this example to Colin E who originally demonstrated it in Silverlight; this is a direct port of that Silverlight code:
Embedded JavaScript isn't enabled, please
visit my blog to see this sample in action!
Deferred vs Rx
Let’s compare the two technologies.
| Deferred | Rx |
Ability to listen for an event and react | | |
Ability to listen to a sequence of occurrences of that event | | |
Use a linq-like syntax to filter and transform a sequence of occurrences | | |
Combine multiple sources to produce a single source | | |
Handle errors in a special way | | |
We can clearly see from this comparison one glaring difference: Rx is designed for the handling of a stream of multiple events, and jQuery Deferred
handles just a single occurrence.
Given that difference, we can conclude that we can translate between Rx and Deferred
in the following way:
- A
Deferred
object can be represented as an Rx Observable
with a single item - Likewise, an Rx
Observable
with a single item can be represented as a Deferred
object - An Rx
Observable
with multiple items can be packaged up into a Deferred
object if it is finite (i.e., we know it will complete, and on completion, we can resolve the Deferred
) - A continuous
Observable
that does not complete, e.g., mouse move events, cannot be represented as a Deferred
because we would not know when to resolve the Deferred
.
Converting Deferred to Rx
As pointed out above, this conversion is pretty simple: a Deferred
object can be represented as an Rx Observable
that spits out a single item and then is completed.
function DeferredAsObservable(deferred) {
var observable = new Rx.AsyncSubject();
deferred.done(function(){
var args = Array.prototype.slice.call(arguments);
observable.OnNext.apply(observable, args);
observable.OnCompleted();
});
deferred.fail(function(){
var args = Array.prototype.slice.call(arguments);
observable.OnError.apply(observable, args);
observable.OnCompleted();
});
return observable;
}
We can consume this conversion function as follows:
var deferred = $.ajax({ url: "test.html" });
var observable = $.DeferredAsObservable(deferred);
observable.Subscribe(function(result){
console.log(result);
});
Converting Rx to Deferred
The conversion from Obervable
to Deferred
is a little more complicated since there could be multiple items in the Observable
. To get around this, we collect all of the items that the Observable
‘pushes’ out until we get a Complete notification. Then we resolve the Deferred
with all of those items.
function ObservableAsDeferred(observable) {
var deferred = $.Deferred();
var results = [];
observable.Subscribe(function(){
var args = Array.prototype.slice.call(arguments);
results.push(args.length === 1 ? args[0] : args);
}, function(e){
var args = Array.prototype.slice.call(arguments)
deferred.reject.apply(deferred, args);
}, function(){
deferred.resolve(results);
});
return deferred.promise();
}
We can use the earlier example of the drawing applcation to show this conversion. Notice the call to ‘Repeat()
‘ at the end of the draggingEvents
Observable
– this caused us to repeatedly listen for mouse moves that occurred between a mousedown
and a mouseup
. If we omit that, then we can just listen to a single line draw (a finite sequence) and convert that to a Deferred
object:
var draggingEvents = mouseMoveEvent.SkipUntil(mouseLeftButtonDown)
.TakeUntil(mouseLeftButtonUp)
.Let(function(mm) {
return mm.Zip(mm.Skip(1), function(prev, cur) {
return {
X2: cur.offsetX,
X1: prev.offsetX,
Y2: cur.offsetY,
Y1: prev.offsetY
};
})
});
var def = $.ObservableAsDeferred(draggingEvents);
def.done(function(mousemoves){
console.log(mousemoves);
}).fail(function(e){
console.log('error!', e);
});
But what if we left in the call to Repeat()
, and the Observable
doesn’t complete? That type of Observable
doesn’t translate to the Deferred
concept – what use is a Deferred
object that is never resolved? In this case, we can only assume that the programmer has made a mistake. Unfortunately, there is no way to detect this scenario so we’ll just let the programmer work it out the hard way!
Rx for jQuery
While we’re on the topic of combining Rx and jQuery, I should point out that Microsoft has already provided us with a library of helper functions to perform the most common tasks – rx.jQuery.js. It extends jQuery with the following functions:
- Eventing:
toObservable
/toLiveObservable
convert a jQuery event to an Observable
(using ‘bind’ and ‘live’ respectively) - Animation:
hideAsObservable
, showAsObservable
, animateAsObservable
, fadeInAsObservable
, fadeOutAsObservable
, slideDownAsObservable
, slideUpAsObservable
, slideToggleAsObservable
– all these provide animations as observables - Ajax:
ajaxAsObservable
, getJSONAsObservable
, getScriptAsObservable
, postAsObservable
, loadAsObservable
: perform Ajax functions as observables
In the above scenarios, rx.jQuery.js provides us with Observable
versions of a number of places where you may otherwise have the implement the Deferred
to Rx code I have given above. Unfortunately, they aren’t brilliantly documented but it is easy enough to inspect the functions in a debugger to deduce their parameters.
The End
I hope you’ve enjoyed this largely academic guide to conversion between Rx and Deferred
. Have fun!
CodeProject