September 2016

Volume 31 Number 9

[Reactive Framework]

Build Asynchronous AJAX-Enabled Web Pages with Reactive Extensions

By Peter Vogel

In an earlier article, I discussed how the observer pattern can be used to manage long-running tasks (msdn.com/magazine/mt707526). By the end of that article, I showed how Microsoft Reactive Extensions (Rx) provide a simple mechanism for managing a sequence of events from a long-running process in a Windows application.

Using Rx just for monitoring a sequence of events from a long-­running task, however, doesn’t take full advantage of the technology. The beauty of Rx is that it can be used for asynchronously integrating any event-based process with any other process. In this article, for example, I’ll use Rx to make asynchronous calls to a Web service from a button click in a Web page (a button click is, effectively, a sequence of one event). To use Rx in the client-side Web environment, I’ll use Rx for JavaScript (RxJS).

Rx provides a standard way to abstract a variety of scenarios and manipulate them using a fluent, LINQ-like interface that lets you compose applications from simpler building blocks. Rx lets you both integrate your UI events with back-end processing while, at the same time, keeping them separate—with Rx you can rewrite your UI without having to make corresponding changes to your back end (and vice versa).

RxJS also supports a clean separation between your HTML and your code, effectively giving you data binding without requiring special HTML markup. RxJS also builds on existing client-side technologies (jQuery, for example). There’s one other benefit to Rx: all Rx implementations tend to look very similar—the RxJS code in this article is very much like the Microsoft .NET Framework code I wrote in my previous article. You can leverage the skills you pick up in one Rx environment in any Rx environment.

Getting Started with RxJS

RxJS achieves its goals by abstracting the parts of an application into two groups. Members of the first group are the observables: fundamentally, anything that fires an event. RxJS provides a rich set of operators for creating observables—observables include anything that can be made to appear to fire an event (Rx can convert arrays into event sources, for example). The Rx operators also let you filter and transform the output of events. It might be worthwhile to think of an observable as a processing pipeline for an event source.

Members of the second group are the observers, which accept results from observables and provide processing for three notifications from an observable—a new event (with its associated data), an error or end-of-the-event sequence. In RxJS, an observer can either be an object with functions to handle one or more of the three notifications or just a collection of functions, one for each notification.

To tie these two groups together, an observable subscribes one or more observers to its pipeline.

You can add RxJS to your project through NuGet (in the NuGet library, look for RxJS-All). One warning, though: When I first added RxJS to a project configured with TypeScript, NuGet Manager asked if I also wanted the relevant TypeScript definition files. Clicking Yes added the files and gave me about 400 “duplicate definition” errors. I’ve stopped accepting that option.

In addition, there are a number of Rx support libraries that provide useful plug-ins for RxJS. For example, RxJS-DOM (available through NuGet as RxJS-Bridges-HTML) provides integration both with events in the client-side DOM and with jQuery. That library is essential for creating responsive Web applications with RxJS.

Like most JavaScript plug-ins for Rx, RxJS-DOM hangs its new features off the Rx object that’s central to the RxJS library. RxJS-DOM adds a DOM property to the Rx object that has multiple useful functions, including several that bridge to jQuery-like functions. For example, to make an AJAX call using RxJS-DOM to retrieve JSON objects, you’d use this code:

return Rx.DOM.getJSON("...url...")

To use both Rx and RxJS-DOM, all you need are these two script tags:

<script src="~/Scripts/rx.all.js"></script>
<script src="~/Scripts/rx.dom.js"></script>

Using both rx.all.js and rx.dom.js is a heavyweight solution because they include all of the functionality from both libraries. Fortunately, both the Rx and RxJS-DOM divide their functionality over several, lighter libraries so you can incorporate just the libraries holding the functionality you need into your page (the documentation on GitHub will tell you, for any operator, to which library that operator belongs).

Integrating a RESTful Service

A typical scenario in a JavaScript application is that the user clicks on a button to retrieve a result from a Web service by making an asynchronous call to the service. The first step in building an RxJS solution is to convert the click event on the button into an observable, something the RxJS/DOM/jQuery integration makes easy to do. This code, for example, uses jQuery to retrieve a reference to an element on the form with an id of getButton and then creates an observable from that element’s click event:

var getCust = $('#getButton').get(0);
var getCustObsvble = Rx.DOM.fromEvent(getCust, "click");

The fromEvent function allows you to create an observable from any event on any element. However, RxJS contains several shortcuts for more “popular” events, including the click event. I could, for example, also have used this code to create an observable from a button’s click event:

var getCustObsvble = Rx.DOM.click(getCust);

I can create a richer processing pipeline for this event that simplifies processing in my application. For example, I might want to avoid handling a scenario where the user clicks the button twice in rapid succession (too quickly, for example, for me to disable the button to prevent the user from doing that). Rather than write a bunch of timeout code, I can handle that by adding the debounce function to the pipeline, specifying that I only want to see clicks that are at least two seconds apart:

var getCustObsvble = Rx.DOM.click(getCust).debounce(2000);

I can also perform some processing on the event using, for example, flatMapLatest, which lets me incorporate a transformation function into the pipeline (something like LINQ SelectMany). The base function—flatMap—performs a transformation on each event in a sequence. The flatMapLatest function goes one step further and handles a typical problem with asynchronous processing: flatMapLatest cancels earlier asynchronous processing if the transformation is invoked a second time and an asynchronous request is still pending.

With flatMapLatest, if I do have a user who clicks the button twice (and more than two seconds apart) then if flatMapLatest is still transforming the previous event, flatMapLatest will cancel the previous event. This eliminates the need for me to write code to handle canceling asynchronous processing.

The first step in using flatMapLatest is to create my transformation function. To do that, I just wrap the getJSON call I showed earlier inside a function. Because my function is tied to a button click, I don’t need any data from the page (in fact, this function barely qualifies as a “transformation” because it ignores the event’s inputs).

Here’s a function that makes a request to a Web API service, using some jQuery along the way to retrieve the customer Id from an element on the page:

function getCustomer()
{
  return Rx.DOM.getJSON("api/customerorders/customerbyid/"
    + $("custId").val());
}

With RxJS, there’s no need to provide callbacks for this request; the results of the call are automatically passed through the observable to any observers.

To integrate this transformation into my processing chain, I just call flatMapLatest from my observer, passing a reference to the function:

var getCustObsvble = Rx.DOM.click(getCust).debounce(2000).flatMapLatest(getCustomer);

I must now subscribe processing functions to the observable. I can assign up to three functions in the subscription: one function to handle the notification when a new event is received, one to report on any error notifications and one to handle a notification sent at the end of the sequence of events. Because my click event processing pipeline is designed specifically to produce a sequence of one event, I don’t need to provide an “end of sequence” function.

The resulting code to assign the two necessary functions might look like this:

var getCustObsvble.subscribe(
  c   => $("#custName").val(c.FirstName),
  err => $("Message").text("Unable to retrieve Customer: "
    + err.description)
);

If I thought I might find this set of functions useful in other places (or just wanted to simplify my subscribe code), I could create an observer object. An observer object has the same functions that I used before, just assigned to properties named onNext, onError and onComplete. Because I don’t need to handle onComplete, my observer object would look like this:

var custObservr = {
  onNext:  c   => $("#custName").val(c.FirstName),
  onError: err => $("#Message").text("Unable to retrieve Customer: "
     + err.description)
};

With a separate observer, the code my observable uses to subscribe the observer becomes simpler:

var getCustObsvble.subscribe(custObservr);

Putting all of this together, I just need a ready function for the page that retrieves the button, attaches the pipeline that turns the button into a very sophisticated observable and then subscribes an observer to that pipeline. Because RxJS implements a fluent interface, I could do it in just two lines of code: one line of jQuery code to retrieve the button element and another line of RxJS code to build the pipeline and subscribe my observer. However, breaking the RxJS pipeline construction and subscribe code over two lines will make the ready function easier for the next programmer to read.

The final version of my ready function would looks like this:

$(function () {
  var getCust = $('#getButton').get(0);
  var getCustObsvble =
    Rx.DOM.click(getCust).debounce(2000).flatMapLatest(getCustomer);
  getCustObsvble.subscribe(custObservr);
});

As a bonus, this process that integrates the click event, the call to the Web service and the data display executes asynchronously. RxJS abstracts all the ugly details of making that work away from me.

Abstracting Event Sequences

By abstracting the process (and providing a rich set of operators) Rx makes many things look alike. This lets you make significant changes to your process without having to make significant structural changes to your code.

For example, because RxJS-DOM treats an observable with one event (a button click) much like an observable that generates a continuous sequence of events (like mousemove), I can make a significant change to my UI without having to do much to my code. For example, I might decide that, rather than have the user trigger the Web service request with a button click, I’ll retrieve the customer data as soon as the user types in the customer’s Id.

The first thing I need to change is the element whose events I’m going to observe. In this case, that means switching from the button on the page to the textbox on the page that holds the customer Id (I’ll also change the name of the variable holding the element):

var getCustId = $('#custId').get(0);

I could turn any number of events for this textbox into an observable. If, for example, I used the blur event on the textbox, I would make my call to the Web service when the user exits the textbox. However, I want to be more responsive than that. I instead choose to retrieve the customer object as soon as the user types “enough” characters into the textbox. That means switching to the keyup event, which generates a sequence of events: one for every keystroke.

That change to my pipeline looks like this:

var getCustObsvble = Rx.DOM.keyup(getCustId)

As the user types in characters, I could end up calling my transformation function multiple times. In fact, if the user types faster than I can retrieve Customer objects, I’m gong to end up with multiple stacked requests. I could count on flatMapLatest to clean up those requests, but there’s a better solution: In my Customer Orders Management system, a customer Id is always four characters long. As a result, there’s no point in calling my transformation function except when there are exactly four characters in the textbox (and, for the record, because my pipeline is now taking customer Id and returning a complete Customer object, my transformation function is now actually doing a “transformation”).

To implement that condition, all I need to do is add the Rx filter function to my pipeline. The filter function works much like a LINQ Where clause: It must be passed by another function (the selector function) that contains a test and returns a Boolean value based on data associated with the latest event. Only those events that pass the test will be passed to the subscribed observer. The filter function will automatically be handed the latest event object in the sequence so, in my selector function’s test, I can use the event object’s target property to retrieve the current value of the textbox and then check that value’s length.

One more change to my pipeline: I’ll remove the debounce function because it’s hard to see what advantage it’s giving me in this new UI. Still, after a significant change to my UI’s interactions, the revised code that creates my observable and subscribes my observers is still structurally identical to my previous version:

var getCustObsvble = Rx.DOM.keyup(getCustId)
                       .filter(e => e.target.value.length == 4)
                       .flatMapLatest(getCustomer);
getCustObsvble.subscribe(custObservr);

And, of course, I don’t have to make any changes to my cust­Observr functions at all: They’re isolated from my UI changes.

I can make one more optional change, this time to my transformation function. My transformation function has always been passed through three parameters—I’ve just been ignoring them when my event source was a button. The first parameter passed to the flatMapLatest transformation function is the event object for the event being observed. I can take advantage of that event object to eliminate the jQuery code that retrieved the customer Id and, instead, retrieve the value of the textbox from the event object. This change makes my code more loosely coupled because my transformation function is no longer tied to a specific element on the page.

My new transformation function looks like this:

function getCustomer(e)
{
  return Rx.DOM.getJSON("api/customerorders/customerbyid/"
    + e.target.value);
}

This is the beauty of the Rx abstraction: Changing from a single-­event-producing element to a sequence-of-events-producing element has only required tinkering with the single line of code that makes up my pipeline (and gave me an opportunity to enhance my transformation function). Of all the changes I’ve made, the one most likely to cause me problems is renaming the variable that holds my input element. This change also emphasizes that, like LINQ, the secret to success with Rx is becoming familiar with the available operators.

Abstracting Web Service Calls

While the Rx abstraction makes changes to the UI look very much alike, a good abstraction should also do the same to the back-end processing. For example, what if the page is revised so that I retrieve not only customer data, but also the customer’s sales orders? To make this interesting, I’ll assume there’s no navigation property that will let me retrieve the sales orders as part of the customer object and I have to make a second call.

With Rx, I first need to create a second transformation function that converts a customer Id into a collection of customer orders:

function getCustomerOrders(e) {
  return Rx.DOM.getJSON("customerorders/ordersbycustomerid/"
    + e.target.value);
}

Then I need to create a second observable that uses this transformation (this code looks very much like the code that creates my customer observable):

var getOrdersObsvble = Rx.DOM.keyup(getCustId)
       .filter(e => e.target.value.length == 4)
       .flatMapLatest(getCustomerOrders);

At this point, you might expect that combining and coordinating these observables could require a considerable amount of work. But, because Rx makes all observables look very much alike, you can combine the output from multiple observables into a single sequence that can be handled by a single (and relatively simple) observer.

For example, if I had several observables retrieving orders from multiple sources, I could use Rx’s merge function to join all of the order into a single sequence to which I could subscribe a single observer. The following code, for example, brings together observ­ables retrieving current orders (getCurrentOrdersObsvble) and posted orders (getPostedOrdersObsvle); it then passes the resulting sequence to a single observer called allOrdersObservr:

Rx.Observable.merge(getCurrentOrdersObsvble, getPostedOrdersObsvble)
             .subscribe(allOrdersObservr);

In my case, I want to do something more interesting: combine an observable retrieving a customer object with an observable retrieving all the orders for that customer. Fortunately, RxJS has an operator for that: combineLatest. The combineLatest operator accepts two observables and a processing function. The processing function you pass to combineLatest is passed the latest result from each sequence and lets you specify how the results should be comingled. In my case, combineLatest is providing more functionality than I need because I have only one result from each observable: the customer object from one observable and all of the customer’s orders from the other.

In the following code, I add a new property (called Orders) to the customer object from my first observable and then put the result from the second observable into that property. Finally, I subscribe an observer called CustOrdersObsrvr to handle my new Customer+Orders object:

Rx.Observable.combineLatest(getCustObsvble, getOrdersObsvble,
                           (c, ords) => {c.Orders = ord; return c;})
             .subscribe(CustOrdersObsrvr);

My custOrdersObservr can now work with the new object I’ve created:

var custOrdersObservr = {
  onNext: co => {
    // ...Code to update the page with customer and orders data...               
  },
  onError: err => $("#Message").text("Unable to retrieve Customer and Orders: "
    + err.description)
};

Wrapping Up

By abstracting the components of an application to just two kinds of objects (observables and observers) and providing a rich set of operators for managing and transforming the results from observables, RxJS provides a highly flexible (and maintainable) way for creating Web applications.

Of course, there’s always a danger with using a powerful library that abstracts complex processes into simple code: When your application does something you didn’t expect, debugging a problem can be a nightmare because you can’t see the individual lines of code that the abstraction has eliminated. Of course, you no longer have to write all that code, either, and (presumably) the abstraction layer will have fewer bugs than the code you would’ve written. It’s the usual trade-off between power and visibility.

By deciding for RxJS, the net benefit to you is that you can make significant changes to your application without having to make significant changes to your code.


Peter Vogel is a systems architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling and database design. You can contact him at peter.vogel@phvis.com.

Thanks to the following Microsoft technical experts for reviewing this article: Stephen Cleary, James McCaffrey and Dave Sexton
Stephen Cleary has worked with multithreading and asynchronous programming for 16 years and has used async support in the Microsoft .NET Framework since the first community technology preview. He is the author of “Concurrency in C# Cookbook” (O’Reilly Media, 2014). His homepage, including his blog, is at stephencleary.com.

Dr. James McCaffrey works for Microsoft Research in Redmond, Wash. He has worked on several Microsoft products including Internet Explorer and Bing. Dr. McCaffrey can be reached at jammc@microsoft.com.


Discuss this article in the MSDN Magazine forum