Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / Javascript

Converting between jQuery Deferred and Rx Observable

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
16 May 2011CPOL5 min read 15.9K   2   2
A guide to conversion between Rx and Deferred

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:

JavaScript
var deferred = $.ajax("hello.html");
deferred.complete(function(result){
	//This function is called when the 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:

JavaScript
function fadeIn(selector) {
	//Create a Deferred object to represent the animation
	var deferred = $.Deferred();
 
	//Fade in, and resolve the Deferred obj when the fade is complete
	$(selector).fadeIn( 1000, deferred.resolve );
 
	//Return the "promise" - a read-only version of the Deferred object
	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.

JavaScript
var canvas = document.getElementById("rxCanvas");
 
//Transform the mouse move event into an observable source of screen coordinates
var mouseMoveEvent = Rx.Observable.FromHtmlEvent(canvas, "mousemove");
 
//Create observable sources from the left button events
var mouseLeftButtonDown = Rx.Observable.FromHtmlEvent(canvas, "mousedown");
var mouseLeftButtonUp = Rx.Observable.FromHtmlEvent(document.body, "mouseup");
 
//Create a 'drag event', which takes the delta in mouse movements 
//when the left button is down.
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");
 
//Subscribe to the observable source and draw lines
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.

DeferredRx
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.

JavaScript
function DeferredAsObservable(deferred) {
 
	//Create a new observable.  The AsyncSubject class is a type of Cold observable,
	//which means that when a user subscribes to the observable, they get items that
	//occurred before the subscription.
	var observable = new Rx.AsyncSubject();
 
	//When the Deferred is complete, push an item through the Observable
	deferred.done(function(){
 
		//Get the arguments as an array
		var args = Array.prototype.slice.call(arguments);
 
		//Call the observable OnNext with the same parameters
		observable.OnNext.apply(observable, args);
 
		//Complete the Observable to indicate that there are no more items.
		observable.OnCompleted();
	});
 
	//If the Deferred errors, push an error through the Observable
	deferred.fail(function(){
 
		//Get the arguments as an array
		var args = Array.prototype.slice.call(arguments);
 
		//Call the observable OnError with the args array
		observable.OnError.apply(observable, args);
		observable.OnCompleted();
	});
 
	return observable;
}

We can consume this conversion function as follows:

JavaScript
//Get some sort of Deferred
var deferred = $.ajax({ url: "test.html" });
//Convert to Observable
var observable = $.DeferredAsObservable(deferred);
//Subscribe to the Observable
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(){
 
		//This is the OnNext callback. Get the arguments and save them.
		var args = Array.prototype.slice.call(arguments);
 
		//If there are multiple args, use the array - 
		//otherwise just push the single arg.
		results.push(args.length === 1 ? args[0] : args);
 
	}, function(e){
 
		//This is the OnError callback.  Reject the Deferred with the arguments.
		var args = Array.prototype.slice.call(arguments)
		deferred.reject.apply(deferred, args);
 
	}, function(){
 
		//This is the OnComplete callback, so we can now resolve the Deferred.
		deferred.resolve(results);
	});
 
	//Return the promise.
	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:

JavaScript
//Create the draggingEvents observable as above - but without the call to Repeat
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
                                            };
                                        })
                                    });
//Convert to Deferred
var def = $.ObservableAsDeferred(draggingEvents);
def.done(function(mousemoves){
	//All done - dump out the mouse move events
	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!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Repstor Ltd
United Kingdom United Kingdom
I am a Product Architect at Repstor.

Repstor custodian and Repstor Provisioning Engine provide case management and provisioning for SharePoint.

Repstor affinity provides uninterrupted access to content systems, like SharePoint through the familiar interface of Microsoft Outlook.

Comments and Discussions

 
QuestionObservables may never complete Pin
jwooley1-Sep-11 2:49
jwooley1-Sep-11 2:49 
AnswerRe: Observables may never complete Pin
Jonathan Cardy1-Sep-11 2:55
Jonathan Cardy1-Sep-11 2:55 
Excellent point, thanks for commenting and pointing it out.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.