Introduction to the Reactive Extensions for JavaScript – Wrapping the Dojo API

Recently in some of the comments I’ve received (keep them coming BTW), I get questions about taking existing APIs and moving them towards using the Reactive Extensions for JavaScript (RxJS).  How can we get away from constant callback hell and move towards composable asynchronous and event-based blocks instead?  In this post, I’m going to walk through how we wrapped the Dojo Toolkit APIs to provide both event handling and AJAX functionality.

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

Wrapping Dojo Events

The first part of connecting the Dojo Toolkit to RxJS is by wrapping the eventing model.  The Dojo Toolkit has the dojo.connect function which allows us to listen in on the execution of either DOM events or even function calls with a listener to be invoked when it is fired.  Conversely, if we wish to stop listening, we can call dojo.disconnect to remove the listener.  The connect method is as follows:

dojo.connect(
    obj, 
    event, 
    context, 
    method, 
    dontFix);

Each parameter is described below:

  • obj – the source object for the event function
  • event – the name of the event, such as “click”
  • context – the object that the listener will receive as “this”
  • method – a listener function to invoke when the event is fired
  • dontFit – prevent the delegation of this connection to the DOM event manager

And the connect function returns a handle for which we can use to disconnect.  The disconnect function takes this handle for us to stop listening to the event.

dojo.disconnect(handle);

We can wrap these events by using the Rx.Observable.Create function which hands us an observer to use and then we return a disposable which allows us to disconnect our listener.  Inside the Create function, we’ll create a handler which takes the event object from Dojo and passes it to the observer.OnNext.  After we’ve defined the handler, we call connect with the parameters and we’ll pass in the handler as the method to invoke.  That function returns to us a handle which we can then use to disconnect the listener.  Finally, we return a function which is invoked when we call Dispose on our subscription.  Below is a wrapped version called Rx.Observable.FromDojoEvent.

var fromDojoEvent = Rx.Observable.FromDojoEvent = 
    function(obj, event, context, dontFix) {
    
    return Rx.Observable.Create(function(observer) {
   
        // Handle on next calls
        var handler = function(eventObject) {
            observer.OnNext(eventObject);
        };
       
        // Connect and get handle for disconnecting
        var handle = dojo.connect(obj, event, context, handler, dontFix);
       
        // Return disposable used to disconnect
        return function() {
            dojo.disconnect(handle);
        };
    });
};

If we want to make it look and feel more like it belongs in Dojo itself, we could also alias it as dojo.connectAsObservable as we so wish.  This part isn’t actually in the codebase just yet, but if the feedback is positive, then it can be.

dojo.connectAsObservable = function(obj, event, context, dontFix) {
    return fromDojoEvent(obj, event, context, dontFix);
};

Below is an example to connect to a given button and then listen for one out of every four clicks.

var obj = dojo.byId("someButton");

var sub1 = Rx.Observable.FromDojoEvent(obj, "click")
    .Sample(4)
    .Subscribe(function() { alert("Fourth click!"); });

// Or

var sub2 = dojo.connectAsObservable(obj, "click")
    .Sample(4)
    .Subscribe(function() { alert("Fourth click!"); });

Wrapping Callback Scenarios

Another part of an API we can wrap is around asynchronous behavior with callbacks, especially for such things as AJAX calls and effects.  For this part, we’re going to cover taking the dojo.io.script.get function and creating an observable from it.  The dojo.io.script.get function is an alternate IO mechanism to dojo.xhrGet that has the capability of doing cross-site data access by dynamically inserting a <script> tag into your web page.  This mechanism also supports JSONP for named callback scenarios.  Let’s look at the signature for the get function.

dojo.io.script.get = function(
    options);

The options takes the following properties:

  • url – The URL to request data
  • callbackParamName – The callback string used for JSONP
  • checkString – A type of check to ensure the script has been loaded. Not used in
  • preventCache – True to apply a unique parameter to prevent the browser from caching
  • content – A JavaScript object of key/value pairs for parameter names and values

For example, we can create the options much like the following:

var jsonpArgs = {
    url: "http://search.twitter.com/search.json",
    callbackParamName: "callback",
    content: { rpp: "100", q: "4sq.com" } };

In order to use the get function, we also have to ensure that we’ve reference the dojo.io.script module such as the following:

dojo.require("dojo.io.script");   

Now we can get down to how we’d wrap the functionality.  First, we’ll create an AsyncSubject, which is used for asynchronous communication that happens once and then caches the value.  Next, we need to handle both the success and error conditions by adding the functions load and error respectively to the options object.  The load function simply passes the data where we call the subject’s OnNext with the data and then mark it as complete with the OnCompleted call.  The error function simply is given an error object so that we can see what went wrong and act accordingly.  We can then call the get function with our options and then finally return our AsyncSubject.

Rx.Observable.FromDojoScriptGet = function(options) {
    // Create subject
    var subject = new Rx.AsyncSubject();
    
    // Handle success
    options.load = function(data) {
        subject.OnNext(data);
        subject.OnCompleted();
    };
    
    // Handle failure
    options.error = function(error) {
        subject.Error(error);
    };
    try {
        // Load the script
        dojo.io.script.get(newOptions);
    }
    catch (err) {
        subject.OnError(error);
    }
    
    return subject;        
}; 

This pattern applies to any callback scenario where we have one value to yield and then we want to cache it.  And now we can query Twitter with the Reactive Extensions with Dojo underneath the covers.  For example, we could query Twitter for those who are posting to FourSquare and has geolocation turned on and get the maximum user ID and then print their text.

dojo.addOnLoad(function() {

    var jsonpArgs = {
        url: "http://search.twitter.com/search.json",
        callbackParamName: "callback",
        content: { rpp: "100", q: "4sq.com" } };

    Rx.Observable.FromDojoScriptGet(jsonpArgs)
        .SelectMany(
            function(data) {
                return Rx.Observable.FromArray(data.results);
            })
        .Where(
            function(result) {
                return result.geo != null;
            })
        .Max(
            function(result) {
                return result.from_user_id;
            })
        .Subscribe(
            function(result) {
                alert(result.text);
            },
            function(error) {
                alert("An error!" + error.description);
            });
});

These two patterns for wrapping events and asynchronous operations doesn’t stop at Dojo, but also applies to the other frameworks out there including YUI3, Prototype, ExtJS, jQuery and so forth.  For the AJAX calls, MooTools is different and we’ll get into that in the next post.

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. 

One important aspect of the Reactive Extensions for JavaScript is how well we play with other frameworks.  By giving out of the box extensions points, this allows for you to still use your framework of choice along with the Reactive Extensions to take care of the event-based and asynchronous behavior.

So with that, download it, and give the team feedback!

3 Comments

Comments have been disabled for this content.