Introduction to the Reactive Extensions for JavaScript – Async Method Chaining
Recently, there was a blog post by Dustin Diaz about method chaining over asynchronous operations. This topic, of course is near and dear to my heart as it strikes the exact chord of what the Reactive Extensions for JavaScript (RxJS) is trying to solve. The ability for us to take asynchronous operations and events and treat them as push collections, we are able to then compose functions together as if they were normal pull collections like arrays. In this post, I’ll walk through the example that was posted on Dustin’s site and show how it can be done using RxJS.
Before we get started, let’s get caught up to where we are today:
- Introduction
- Creating Observables
- Creating Observers
- jQuery Integration
- Composing Callbacks
- From Blocking to Async
- Wikipedia Lookup
- Composing Deeper
- Bing Maps and Twitter Mashup
- Drag and Drop
- jQuery Live Event integration
- jQuery AJAX integration
- A Separation of Concerns
- Aggregates – Part 1 and Part 2
- Join Operators
- Going “Parallel” with ForkJoin
- Refactoring a Game
Asynchronous Method Chaining
To show off some of the power of the Reactive Extensions for JavaScript, let’s take the example of creating a paragraph, populating it from a single tweet from Twitter, adding a class and finally appending it to a div element. Just as well, we want to optionally give you a way to gracefully handle errors should the data source not be available. And when has that ever happened to Twitter, I mean really??!!
$("<p/>") .fetch("http://search.twitter.com/search.json?q=4sq.com&rpp=1") .addClass("loading") .appendTo("#content"); // Or var errHandler = function(exn) { // Do something with the exception } $("<p/>") .fetch("http://search.twitter.com/search.json?q=4sq.com&rpp=1", errHandler) .addClass("loading") .appendTo("#content");
This is fairly simple to do through the use of a little jQuery and RxJS knowledge. First, we’ll extend the jQuery fn with a function called fetch which takes a URL and an optional onError handler which is a function that takes an exception and returns no value. Depending on whether the error handler was supplied or not, we’ll create an observer from the onNext which sets the tweet into the innerHTML of the jQuery selector. We’ll then call the ajaxAsObservable extension which comes with RxJS to turn the ajax function into an observable, and subscribe with our observer. Finally, we return the context of the selector as the result of the function which allows us to continue chaining calls.
(function($) { $.fn.fetch = function(url, onError) { var collection = this; var onNext = function(d) { $(collection).html(d.data.results[0].text); } var observer = onError ? Rx.Observer.Create(onNext, onError) : onNext; $.ajaxAsObservable({ url: url, type: 'get', dataType: 'jsonp', }) .Subscribe(observer); return this; }; })(jQuery);
That’s a fairly simple scenario for using this, so, how about something a bit more complex like fetching the results at a given interval? We could then create a function called fetchEvery which takes a specified interval to call the AJAX service and populate the selector by using the Interval method with our desired interval.
$.fn.fetchEvery = function(url, interval, onError) { var collection = this; var onNext = function(d) { $(collection).html(d.data.results[0].text); } var observer = onError ? Rx.Observer.Create(onNext, onError) : onNext; Rx.Observable.Interval(interval) .Select(function() { return $.ajaxAsObservable({ url: url, type: 'get', dataType: 'jsonp', }); }) .Switch() .Subscribe(observer); return this; };
And then we could use it much like the following where we fetch the results every 5 seconds.
$("<p/>") .fetchEvery("http://search.twitter.com/search.json?q=4sq.com&rpp=1", 5000) .addClass("loading") .appendTo("#content");
But what about further transforming the data as it comes back, such as the example of censoring the swear words and create links from the text? Well, instead of living inside jQuery and sprinkling in RxJS like we have been doing for the past couple of examples, we can then decide that instead we’ll live inside RxJS and sprinkle in jQuery. We’ll create functions to censor the text, linkify it, handle any exceptions and finally handle the text as it comes in so that we can add it to the content.
function handleText(text) { $("<p/>").html(text).addClass("loading").appendTo("#content"); } function errHandler(exn) { // handle exception } function censorTweet(uncensored) { // handle censoring } function linkify(tweet) { // linkify tweet } $(document).ready(function() { $.ajaxAsObservable({ url: "http://search.twitter.com/search.json?q=4sq.com&rpp=100", type: "get", dataType: "jsonp", }) .Select(function(d) { return d.data.results[0].text; }) .Select(censorTweet) .Select(linkify) .Subscribe(handleText, errHandler); });
This gives us the ability now to get the data from Twitter, censor it if necessary, create real links from the text, and finally put the results into our content in a fluent style while mixing in jQuery where we need to. Just as well, I could have used any other library including Dojo, ExtJS, YUI, Prototype, Moo Tools or whatever I felt like, so I’m not particularly tied here to a certain library and I get the same win from each.
Conclusion
Dealing with asynchronous programming has been in the forefront of many minds in the JavaScript community. At JSConf, there were several examples of frameworks trying to get around the idea of callbacks and instead lean more towards composition. By utilizing the Reactive Extensions for JavaScript, we’re able to compose together asynchronous and event-based operations together and transform the results in interesting ways. We can also live well inside the given frameworks such as jQuery and sprinkle in RxJS, or we can live in RxJS and sprinkle in the calls to the various libraries such as jQuery, Moo Tools, Prototype, and so forth.
So with that, download it, and give the team feedback!