Introduction to the Reactive Extensions for JavaScript – Extending jQuery AJAX

So far we’ve come a long way in this series, starting with some of the basics, and then going deeper with composition with such things as the Bing Maps and Twitter Mashup, Drag and Drop, asynchronous programming among others. 

One of the nicest aspects so far is the ease of integration into jQuery.  For the first release, we have Observable.FromJQueryEvent, but we didn’t cover such things as Live Events, which are coming soon as well as other such calls that we’ll get into in today’s post.  But adding all of this could tend to clutter up the main Reactive Extensions for JavaScript core functionality, so instead, we’ll break out the functionality into the core library and then have extensions in other files to cover DOM events, jQuery integration and so on.  We’ll go into more details about what those other libraries are in subsequent posts.

Before we get started, let’s get caught up to where we are today:

Extending jQuery AJAX Calls

In the previous post, we covered not only the jQuery event integration with the bind and unbind, but also support for integration with live events.  Outside of events and live events, there are other ways we could integrate the Reactive Extensions for JavaScript and jQuery, including all functions which take some form of a callback.

For example, we could look at wrapping the jQuery AJAX API to take advantage of RxJS and that’s precisely what we’ve done is to include the AJAX wrappers in an upcoming Reactive Extensions for JavaScript release.  To give you an idea of what we’ve done, let’s first look at the jQuery function getJSON, which allows us to load JSON encoded data from a given URL using an HTTP GET request.  This function takes in a URL, an optional map or string of data to be sent with the request, and finally a callback with the data and the text status should it succeed.

jQuery.getJSON( 
    url, 
    [ data ], 
    [ callback(data, textStatus) ] )

We can use this function to, for example, to call the Twitter search API to look for users of FourSquare, iterate through the results and put each result in an unordered list.

var url = "http://search.twitter.com/search.json";
var term = "4sq";

$.getJSON(url, { rpp : 100, q : term }, function(d) {

    $.each(d.results, function(_, result) {
        $("#twitterList")
            .append("<li>" + result.created_at + "-" + 
                             result.from_user + " : " + 
                             result.text + "</li>");
    });
});

When we’re using the getJSON function though, what we’re really doing is calling the underlying ajax function which takes a settings map which contains our URL, the data and a success continuation.

$.ajax({
  url: url,
  dataType: 'json',
  data: data,
  success: success
});

This is great for simple examples, but what about error handling?  Unfortunately, as written, it does not handle such scenarios.  Instead, we could, through the use of the Reactive Extensions for JavaScript, rewrite the AJAX functionality so that we get composable behavior with these callback scenarios.  To do this, we could wrap not only the success function, but the error as well and handle each appropriately.  We’ll concern ourselves with two functions on our settings maps, the success and the error. 

The success function is a function which is called upon the successful completion of the AJAX operation.  This function gets passed three arguments, the data, the text status and the XMLHttpRequest object and looks much like the following:

success(
    data, 
    textStatus, 
    XMLHttpRequest) 

On the other side of the story, we have the error function which is called when our AJAX call fails.  This function gets passed three arguments as well, the XMLHttpRequest object, the text describing the error, and optionally the error object itself.

error(
    XMLHttpRequest, 
    textStatus, 
    errorThrown) 

Now that we have a basic understanding of the two functions we’re interested in as part of our settings object, let’s look how we rewrote the ajax function as an observable, in this case calling it ajaxAsObservable.  We’ll take the take a settings map much like the original ajax function does, copy out the values into a new map and add the success and error handlers.

$.ajaxAsObservable = function(settings)
{
    var internalSettings = {};
    for (var setting in settings) {
        internalSettings[setting] = settings[setting];
    }
   
    var subject = new Rx.AsyncSubject();
    internalSettings.success = function(data, textStatus, xmlHttpRequest) {
        subject.OnNext({ 
            data: data, 
            textStatus: textStatus, 
            xmlHttpRequest: xmlHttpRequest });
       subject.OnCompleted();
    };
   
    internalSettings.error = function(xmlHttpReuqest, textStatus, errorThrown) {
        subject.OnError({ 
            xmlHttpRequest: xmlHttpRequest, 
            textStatus: textStatus, 
            errorThrown: errorThrown });
    };
   
    $.ajax(internalSettings);
   
    return subject;
};

As you can see, it’s a fairly common approach for us to use the AsyncSubject to wrap a lot of these APIs.  We also defined a shorthand function just as jQuery did with getJSON aptly named getJSONAsObservable.

$.getJSONAsObservable = function(url, data)
{
    return $.ajaxAsObservable({ 
        url: url, 
        dataType: 'json', 
        data: data });
};

Now to show this in action, we could create an observable which searches Twitter for FourSquare users and only those with geolocation enabled, display the tweeted text as well as their latitude and longitude.

Array.prototype.toObservable = function() {
    return Rx.Observable.FromArray(this);
}

var url = "http://search.twitter.com/search.json";
var term = "4sq";

$.getJSONAsObservable(url, { rpp : 100, q : term })
    .SelectMany(function(d) { return d.data.results.toObservable(); })
    .Where(function(result) { return result.geo != null; })
    .Subscribe(function(result) {
        var lat = result.geo.coordinates[0];
        var lon = result.geo.coordinates[1];

        $("#twitterList").append("<li>" +
            result.created_at + "-" +
            result.from_user + " : " +
            result.text + "<br />" +
            "at " + lat + " - " + lon + "</li>");
    });

As you may have noticed, not once did we actually use naming of Rx.Observable naming, and instead we’re focusing more on integrating naturally with jQuery including the naming as well as extending the jQuery object itself.  Your thoughts and feedback on these designs are always appreciated.

Conclusion

Combining jQuery and the Reactive Extensions for JavaScript gives us even more options to deal with not just events but AJAX callbacks as well.  That’s just one of the many things we can do with it that I’ll hopefully cover more in the near future.  So, download it, and give the team feedback!

What can I say?  I love JavaScript and very much looking forward to the upcoming JSConf 2010 here in Washington, DC where the Reactive Extensions for JavaScript will be shown in its full glory with Jeffrey Van Gogh (who you can now follow on Twitter).  For too many times, we’ve looked for the abstractions over the natural language of the web (HTML, CSS and JavaScript) and created monstrosities instead of embracing the web for what it is.  With libraries such as jQuery and indeed the Reactive Extensions for JavaScript gives us better tools for dealing with the troubled child that is DOM manipulation and especially events.

No Comments