Introduction to the Reactive Extensions for JavaScript – Conditionals
After spending the past couple of posts talking about how we integrate and why, let’s get back to the basic operators of the Reactive Extensions for JavaScript. This time, we’ll cover conditional logic that we can do with RxJS (which also applies to Rx.NET as well). With traditional LINQ, we have the Where operator which allows us to filter operations, but it doesn’t allow us to do one operation or another easily. Instead, the Reactive Extensions team has included two operators, If and Case which allow us some flexibility on how we want to execute conditional logic.
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
- Async Method Chaining
- Custom Schedulers
- Countdown Timers
- Wrapping the Dojo Toolkit
- MooTools Integration
Conditional Branching with Observable.If
The first conditional operator we’ll cover is the Observable.If function. This function acts as a normal if statement would in that it allows us to specify a conditional function and then have a branch for the “then” logic and another branch for the “else” logic. The signature of the function is below:
// condition : () -> bool // thenSource : Observable // elseSource : Observable // returns : Observable Rx.Observable.If( condition, thenSource, elseSource);
For example, I could determine whether I want to use the cached value of an observable or start a fresh request to get some data based upon some setting.
var cachedObservable = new Rx.Subject(); ... Rx.Observable.If( function() { return shouldCache; }, cachedObservable Rx.Observable.Defer( function() { return $.getJSONAsObservable("someurl"); }) );
You’ll notice that I deferred the getJSONAsObservable and there’s a very good reason for that. Such AJAX calls as this one are hot observables which mean that they fire immediately instead of waiting for the Subscribe call being made. We’ll get into the whole hot versus cold observables in another post.
So, you might be thinking, what’s the difference then between this and if I had written it the old fashioned way using an if statement?
var cachedObservable = new Rx.Subject(); ... if (shouldCache) { return cachedObservable; } else { return Rx.Observable.Defer( function() { return $.getJSONAsObservable("someurl"); }); }
Well, the difference between the two is that the Rx.Observable.If function will evaluate the condition on subscription, whereas the if statement will evaluate eagerly for example on a hot observable. The if statement is nice, but what if we have more than just a simple binary case?
Conditional Branching with Observable.Case
Another mechanism we have for branching logic is be being able to handle a switch statement. What we want to give is the ability to write your traditional switch statement, but have it lazily execute just as we have the If function above. We can take the following as an example where based upon n, return the appropriate Observable value.
switch(n) { case 1: return Rx.Observable.Return(1); case 2: return Rx.Observable.Return(2); default: return Rx.Observable.Return(3); }
In order to make this happen, we need to use the Rx.Observable.Case function which allows us to specify a condition, such as checking for the value of n, a hash object which contains key/value pairs with the key and their Observable value, and finally an optional default case.
// selector : () -> Key // sources : Dictionary<Key, Observable> // defaultSource : Observable // returns : Observable Rx.Observable.Case( selector, sources, defaultSource);
In the case of our above switch statement, we’ll first want to create the hash of our sources. Basically, the property name is the key and its value is the value of the key/value pair.
var sources = { 1 : Rx.Observable.Return(1), 2 : Rx.Observable.Return(2) };
Now that we have the sources, let’s finish off the implementation. First, for our selector, we’ll need to provide a function which takes no parameters and returns our desired key. Next, we’ll provide the sources from above, and finally we’ll provide a default case if no others match.
var cased = Rx.Observable.Case( function() { return n; }, sources, Rx.Observable.Return(3));
So, in the case of n being 1, then the first source will be selected, and so on for those along the line. If n does not match either 1 nor 2, the defaultSource will be selected. Let’s run through a quick example where we check if the detected language is a language we support, either French or Spanish, and if we do, then we translate. Else, we will go ahead and throw an exception via the Rx.Observable.Throw function as part of the workflow to say that the given language isn’t supported.
// The variables could be something like: // var detected = "fr"; // var text = "Nous allons traduire un texte"; var sources = { es : Rx.Observable.Defer( function() { return translateFromSpanish(text); }), fr : Rx.Observable.Defer( function() { return translateFromFrench(text); }) }; Rx.Observable.Case( function() { return detected; }, sources, Rx.Observable.Throw("Not supported!")) .Subscribe( function(data) { alert(data); }, function(err) { alert("An error occurred: " + err); });
And there you have it, you now have two ways of dealing with lazy conditional logic within your observable workflow.
Back to .NET
Even though my examples here are primarily in JavaScript, we have the exact same functionality in the Reactive Extensions for .NET and Silverlight. For example, the If function is implemented as well with the signature as follows:
public static IObservable<TResult> If<TResult>( Func<bool> condition, IObservable<TResult> thenSource, IObservable<TResult> elseSource )
And a simple example of using this, such as an even/odd scenario:
Observable.If<string>( () => n % 2 == 0, Observable.Return("even"), Observable.Return("odd"));
Just as well, we can use the Case statement, which has the following signature:
public static IObservable<TResult> Case<TValue, TResult>( Func<TValue> selector, IDictionary<TValue, IObservable<TResult>> sources, IObservable<TResult> defaultSource )
And then we could walk through a simple example of creating our cases and then call the case with our variable n which could be either our 1, 2 or anything else.
var sources = new Dictionary<int, IObservable<int>> { { 1, Observable.Return(1) }, { 2, Observable.Return(2) } }; var cased = Observable.Case<int>( () => n, sources, Observable.Return(3));
As with most examples I give on this blog, most of the core operators work nicely when going back and forth from RxJS to RxNET.
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.
When we start creating more advanced workflows through the Reactive Extensions, we need powerful ways of expressing such concepts as conditional logic outside of the Where function we’re already accustomed to. With the If and Case functions, we have two powerful pieces at our disposal to make those more complicated pieces a reality.
So with that, download it, and give the team feedback!