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.
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:
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:
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, eg. 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.
Deferred vs Rx
Let's compare the two technologies.
|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 (ie. we know it will complete, and on completion, we can resolve the Deferred)
- A continuous Observable that does not complete, eg. 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.
We can consume this conversion function as follows:
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.
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:
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 have 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 - these all 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.
I hope you've enjoyed this largely academic guide to conversion between Rx and Deferred. Have fun!CodeProject