Introduction to the Reactive Extensions for JavaScript – Composing Callbacks
So far in this series, we’ve covered some basic information about the Reactive Extensions for JavaScript (RxJS) including creating observables and creating observers as well as jQuery integration. Now that we have a foundation in some of the basic building blocks, let’s actually do something interesting with it. For example, how would we compose two AJAX calls together with callbacks? In this post, we’ll explore using the Bing Translator in combination with jQuery and the RxJS.
Before we get started, let’s get caught up to where we are today:
Detecting and Translating Text with the Microsoft Translator
One of the many examples I’ve played with involving asynchronous programming, especially with my F# samples as well as the Reactive Extensions for .NET is using the Microsoft Translator. This API, which is provided in several flavors (SOAP, AJAX and HTTP) allows us to not only translate but also detect the language of a given piece of text. Just as well, there are other functions such as getting all languages codes and getting all language names by language code, and we’ll cover that in a subse)quent post. Getting started, you need to apply for a developer key which you can get here. From there, we can embed our JavaScript link in our HTML so that we can get started.
<script type="text/javascript" src="http://api.microsofttranslator.com/V1/Ajax.svc/Embed?appId=myAppId"> </script>
In this example, we’re going to first detect the language of the given piece of text and then translate it into another language. In honor of Erik and his team, I decided on Dutch as the destination language. In order to detect the language we need to call the detect function which looks like the following. It takes in some text and then a callback which then will give us our result as the function parameter.
Microsoft.Translator.detect( text, // Text of the unknown language callback); // A callback on complete
Next, we need the ability to translate our text to Dutch once we’ve determined the source language. In order to do so, we must call the aptly named translate function which we provide our text, a from language that we’ll provide from our detect function callback, our destination language and finally a callback which provides us with the resulting text.
Microsoft.Translator.translate( text, // Text to translate from, // The source language to, // The destination language callback); // The handling callback
What you’ve noticed from these two functions is that they provide callback functions for each. If we want truly compositional behavior, our code could get quite messy especially if we’re doing anything such as filtering results, transforming, aggregating and so forth. But for now, we’ll have a simple example of composing these callbacks together. In order to do so, we have to turn these two functions, detect and translate into Observables. So, how do we do that?
In this case, we’ll make use of the AsyncSubject class which allows us to represent the result of an asynchronous operation. This class will take one value and only one value and then caches it for all future calls and acts as both an Observer and an Observable. So for each time we call this subsequently, it will not cause a side effect and instead yield us the cached calculated value. We’ll then call the detect method with our text and a callback, and inside of our callback, we’ll yield the value with OnNext and then mark our sequence as completed by calling OnCompleted. Finally, we’ll return our AsyncSubject as an Observable and hide the fact that it’s both an Observer and Observable.
function detect(text) { var subject = new Rx.AsyncSubject(); Microsoft.Translator.detect( text, function(result) { subject.OnNext(result); subject.OnCompleted(); }); return subject.AsObservable(); }
The same also applies to our translate function in pretty much the same form as we have above.
function translate(text, from, to) { var subject = new Rx.AsyncSubject(); Microsoft.Translator.translate( text, from, to, function(translation) { subject.OnNext(translation); subject.OnCompleted(); }); return subject.AsObservable(); }
Now, let’s compose these together in such a way that we first detect the language of the text and then translate. To make that happen, we’ll use the SelectMany method which projects each value of our observable sequence to an observable sequence and flattens the resulting observable sequences into one observable sequence. In this case, we’re only projecting one value to the next observable sequence.
var translator = detect(translateText) .SelectMany(function(detected) { return translate(translateText, detected, "nl"); });
Of course we could do more like filter, scan, etc with this, but it’s a good start. Now bringing it all together in a jQuery and RxJS way, we can now complete our example by wiring up our HTML to take the input from a textbox once a button has been clicked, translate it, and display the result.
$(document).ready(function() { $("#translateCommand") .ToObservable("click") .Subscribe(function(event) { var translateText = $("#translateText").val(); var translator = detect(translateText) .SelectMany(function(detected) { return translate(translateText, detected, "nl"); }); translator.Subscribe(function(translated) { $("#translatedText").html(translated); }); }); });
And now we can see it in action by entering a sentence and sure enough it detects it as English properly.
How can I be sure of that? What if I want to perform a side effect during the process, such as displaying our detected language? To do that, we can use the Do method which invokes some sort of action for its side effect for each item in the sequence. We could then change our translator Observable to the following to enable that behavior.
var translator = detect(translatedText) .Do(function(detected) { $("#detectedText").html("Detected " + detected); }) .SelectMany(function(detected) { return translate(translatedText, detected, "nl"); });
Now we have a nice compositional and quite fluent chain which deals with asynchronous behavior. Invoking it this time in Spanish, we get the following result.
We could go even further to aggregate counts of which language was detected over time by using the Scan function, but that’s for another time.
Conclusion
Through the use of the Reactive Extensions for JavaScript, we’re able to bring compositional behavior to that which had been harder to do, such as asynchronous callbacks. We’re able to get code in a nice fluent manner that makes sense and let’s us focus on the core problem domain instead of scattering code within a bunch of callbacks which can lead to any number of race conditions.
This of course is only scratching the surface of what capabilities this library has and there is much more yet left to cover. The question you’re probably asking now is where can I get it? Well, for that you’ll have to stay tuned. I hope to have more announcements soon about its general availability.
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.