Udostępnij za pośrednictwem


RxJS 2.2 Released

As announced earlier this week by MS Open Tech, the Reactive Extensions for JavaScript (RxJS) version 2.2 was released.  This was a big release which encompassed a number of changes as you’ll see in this post.

Documentation

Perhaps the biggest change of all is the documentation which can be found here.  This complete set of documentation not only includes the complete RxJS API, but also many things to help get you started with RxJS such as:

All of this is a work in progress, and we’ve had a tremendous amount of support from the community in helping our documentation and examples.  Have a sample you want to contribute or some documentation on mapping from your favorite library?  Send us a pull request and we’ll work with you to get it included!

Compatibility

RxJS has strived for in the past to support both modern browsers in addition to legacy browsers dating back to IE6.  Like many other projects like jQuery, Lo-Dash among others have started to break with the past, but to also support legacy browsers.  RxJS is no exception to this and as of version 2.2, rx.js will now only work with ES5 compatible browsers.  In order to continue support for legacy browsers, we will ship rx.compat.js which adds polyfills for older browsers.

Batteries Included

Starting with this release, we made a number of changes, moving non-pertinent things out of the core rx.js file, such as the notion of virtual time and asynchronous functions.  This resulted in the creation of two new files, rx.async.js and rx.virtualtime.js.

rx.async.js

A common request for RxJS was to include some bridges to such things as events, callbacks and Promises all in the core library.  We’ve enabled this through the rx.async.js library to enable you to bind to any number of sources.  By default, rx.async.js only works with ES5 compliant browsers, but as we mentioned above, backwards compatibility is important to us, so we’ve also included rx.async.compat.js which normalizes event behavior between browsers.

You can now bind to events using Rx.Observable.fromEvent

 var block = document.getElementById('block');

// Get all events
var mousemoves = Rx.Observable.fromEvent(block, 'mousemove'),
    mousedowns = Rx.Observable.fromEvent(document, 'mousedown'),
    mouseups = Rx.Observable.fromEvent(block, 'mouseup');

// Create a drag event
var drag = mousdowns.selectMany(mousemoves.takeUntil(mouseups));

Alternatively, there may be cases in which you would like to use existing libraries or patterns to create events.  To that end, we’ve created Rx.Observable.fromEventPattern, which takes two functions, one for subscribe and one for unsubscribe.

In this example, we can wrap jQuery’s on method using Rx.Observable.fromEventPattern .

 var $input = $('#input');
var data = { name: 'RxJS' };

var clicks = Rx.Observable.fromEventPattern(
    function add (h) {
        $('input').on('click', data , h);
    },
    function remove (h) {
        $('input').off('click', h); 
    });

clicks.subscribe(function (e) {
    console.log(e.data.name);
});

Promises are a great pattern for dealing single value asynchronous computations.  They can be found in any number of libraries such as Angular, jQuery, Dojo, WinJS, and even libraries dedicated to them such as Q or when.js.  Promises are even making their way into the next version of ECMAScript.  Naturally, we would like a way to bridge natively towards a common standard of the Promises A+ spec in RxJS.

In this example, we can query Wikipedia by wrapping the jQuery ajax method with the Rx.Observable.fromPromise method.

 function searchWikipedia (term) {
    var promise = $.ajax({
        url: 'https://en.wikipedia.org/w/api.php',
        dataType: 'jsonp',
        data: {
            action: 'opensearch',
            format: 'json',
            search: encodeURI(term)
        }
    }).promise();

    return Rx.Observable.fromPromise(promise);
}

Callbacks are also a way of life with JavaScript, especially true in Node.js.  In RxJS version 2.2, you have multiple ways of binding to callbacks, first a standard callback, and then secondly a Node.js-style callback in which the error is always the first parameter to the callback function.

In this first example, we can determine whether a file exists by wrapping the fs.exists function, which has a callback which only returns a Boolean as to whether the file exists.  Since it only has a single value and no error, we can use the Rx.Observable.fromCallback method.

 var Rx = require('rx');
var fs = require('fs');
var path = require('path');

var fileName = path.join(__dirname, 'file.txt');

// Wrap the function
var exists = Rx.Observable.fromCallback(fs.exists);

// Call the function and then subscribe to get the result
exists(fileName).subscribe(function (result) {
    console.log('File exists? ' + result);
});

That covers one case, but we want to handle cases in which errors are automatically handled for us by RxJS.  We can wrap Node.js error style callbacks easily with the Rx.Observable.fromNodeCallback method.  The next example, we’ll wrap the fs.stat function to get the file statistics. 

 var Rx = require('rx');
var fs = require('fs');
var path = require('path');

var file = path.join(__dirname, 'file.txt');

// Wrap stat
var stat = Rx.Observable.fromNodeCallback(fs.stat);

// Run stat, get results and check for errors
var subscription = stat(file).subscribe(
    function (fsStat) {
        console.log('Is file? ' + fsStat.isFile());
    },
    function (err) {
        console.log('Error: ' + err);
    }
);

In addition to this, we also moved Rx.Observable.toAsync and Rx.Observable.start out of core and into rx.async.js.

rx.virtualtime.js

As above, we are very conscious of the size of files that we ship as part of RxJS, so another piece that was removed from core was the notion of virtual time.  Both the VirtualTimeScheduler and HistoricalScheduler were moved to this file.

In order to continue to make your tests work, you will need to add rx.virtualtime.js along with rx.testing.js as shown here.

 <script src="rx.js"></script>
<script src="rx.virtualtime.js"></script>
<script src="rx.testing.js"></script>

<script>
test('map should produce values', function () {
    var scheduler = new TestScheduler();

    // Set up sequence to run through  
    var xs = scheduler.createHotObservable(
        Rx.ReactiveTest.onNext(210, 1),
        Rx.ReactiveTest.onNext(220, 2),
        Rx.ReactiveTest.onCompleted(230)
    );

    // Run computation
    var results = scheduler.startWithCreate(function () {
        return xs.map(function (x) { return x * x; });
    });

    // Assert using custom assertions
    assertCollectionEqual(results, [
        Rx.ReactiveTest.onNext(210, 1),
        Rx.ReactiveTest.onNext(220, 4),
        Rx.ReactiveTest.onCompleted(230)
    ]);
});
</script>

This also gives us more flexibility to define more virtual time based schedulers in the future.

Going Lite

A common ask of the team is to create a “bite sized” version of RxJS which contains most of the operators that one would need on a daily basis.  Instead of the a la carte tradition of RxJS, using only the pieces you need, this build has the most commonly used operators from factory methods, standard query operators, time-based operators, binding operators such as multicast, and async operators such as Rx.Observable.fromEvent, Rx.Observable.fromPromise and more in the form of rx.lite.js.  We also have a compatibility version which supports older browsers in rx.lite.compat.js.  The size of the entire library is about 7kb gzipped, which offers a lot of possibilities without much cost.

With this capability, we now have the ability to write our complete autocomplete example without needing any external library other than one to provide us Ajax support.

 <script src="jquery.com"></script>
<script src="rx.lite.js"></script>
<script>
// Search Wikipedia using jQuery JSONP
function searchWikipedia (term) {
    var promise = $.ajax({
        url: 'https://en.wikipedia.org/w/api.php',
        dataType: 'jsonp',
        data: {
            action: 'opensearch',
            format: 'json',
            search: encodeURI(term)
        }
    }).promise();
    return Rx.Observable.fromPromise(promise);
}

var $input = $('#input'),
    $results = $('#results');

// Get the data from the text field
var text = Rx.Observable.fromEvent(input, 'keyup')
    .map(function (e) {
        return e.target.value;
    })
    .filter(function (text) {
        return text.length > 2;
    })
    .throttle(500)
    .distinctUntilChanged();

var subscription = text.flatMapLatest(searchWikipedia).subscribe(
    function (results) {
        // Bind results to UI
    },
    function (err) {
        // Handle error
    });
</script>

We’re working on making custom builds so that you can create your own lite version using the operators that you need.  Stay tuned for that.

Breaking Changes

With all of these changes, we had a few breaking changes, for example:

  • Rx.Observable.toAsync moved to rx.async.js
  • Rx.Observable.start moved to rx.async.js
  • Rx.HistoricalScheduler moved to rx.virtualtime.js
  • Rx.VirtualTimeScheduler moved to rx.virtualtime.js

In addition, the behavior of Rx.Observable.prototype.scan was also changed as it was in the Reactive Extensions for .NET.  This is to reflect behavior found in other languages such as F#, Haskell and others.  The basic rule is, if you have an empty observable sequence with a seed, then you should yield that seed, else if it is an empty sequence with no seed, it should be an empty sequence.  This breaks from the old behavior which yielded nothing with an empty observable sequence even when it had a seed.

 function add(x, y) {
    return x + y;
}

// With seed
Rx.Observable.empty().scan(add, 0)
    .subscribe(console.log.bind(console));
// => 0

// Without seed
Rx.Observable.empty().scan(add)
    .subscribe(console.log.bind(console));
// => Nothing

We will try in the future to keep breaking changes to a minimum as we continue to innovate with the Reactive Extensions.

Get It Now

There are many ways of getting RxJS, including through:

We’re Not Done Yet

Even though we accomplished a lot in this release, we are by no means done.  In fact, we have plenty that we are researching for our next version which could include the following:

  • Query expression parsing to create LINQ to IndexedDB, OData, etc
  • Backpressure to handle situations when the Observer cannot keep up
  • And much more.

As always, we’d like to hear from you as to what’s important with the Reactive Extensions for JavaScript, so let us know through our forum, our blogs, and our CodePlex and GitHub repos.

Conclusion

We were able to accomplish quite a bit with this release of RxJS 2.2, and the future is quite bright with it.  We are working with our partners including Netflix on how we can improve the library, and best of all, make it approachable for just about anybody.  We’d love to hear what you think!

Comments

  • Anonymous
    December 13, 2013
    Wow... let me be the first to congratulate you. Kudos!

  • Anonymous
    December 14, 2013
    Thanks, great job. Would be nice to have more examples, especially with schedulers.

  • Anonymous
    December 14, 2013
    Well  done to the team, awesome stuff as usual. Query expressions in js sounds really cool. Have you  seen http://esprima.org ? I wonder if it could help to combine this with the native ability from js to access the source code of functions programmatically. Exciting times!! Imagine if we'd could send a reactive query expression to the server and get it to stream automagically through signal r. Just dreaming here but rx js is already awesome today.

  • Anonymous
    December 15, 2013
    @Slavah, What examples in particular would you like to see?  We have other examples of using schedulers directly here: github.com/.../RequestAnimationFrame Also we have documentation on Schedulers as well: github.com/.../schedulers.md

  • Anonymous
    December 15, 2013
    @Clement, Yes, we are looking into that among other solutions.  Yes, that is the overall goal of what we're trying to do is send over the query expression to the server via JavaScript.