Reactive Programming

with RxJS

A short introduction to

my last big project

  • Windows desktop app (C#)
  • Used by analysts to examine statistical data
  • UI highly context sensitive
  • Common case: one Checkbox.Enabled depends on 7 other elements. A bit like this, only more so:

var checkShowVariable = document.getElementById("checkShowVariable");
var checkShowValue    = document.getElementById("checkShowValue");
var checkShowOperator = document.getElementById("checkShowOperator");

function updateShowOperatorEnabled() {
  var enabled = (checkShowVariable.checked && checkShowValue.checked);

  if (enabled )
    checkShowOperator.removeAttribute("disabled");    
  else
    checkShowOperator.setAttribute("disabled",true);
}

checkShowVariable.onchange = updateShowOperatorEnabled;
checkShowValue.onchange    = updateShowOperatorEnabled;

Why is this bad?

  • Relationship is expressed twice (event wiring & update method)
  • Hard to reason about interdependencies, prone to errors
  • Events are not composeable

We need "pipelines" for events

  • maybe a bit like the pipelines we build with functional style libraries like underscore et al?
const source = [1, 2, 3, 4, 5];
const filtered = [];

for (let i = 0; i < source.length; i++)
  if (source[i] % 2 === 0)
    filtered.push(source[i]);

for (let x of filtered)
  console.log(x); 
const filtered = _([1, 2, 3, 4, 5])
                  .filter(x => x % 2 === 0);

for (let x of filtered.value())
    console.log(x);
  • functional style is declarative
  • tends to express the What instead of the How

Hello reactive programming!

var checkShowVariable = document.getElementById("checkShowVariable");
var checkShowValue    = document.getElementById("checkShowValue");
var checkShowOperator = document.getElementById("checkShowOperator");

// Create Observables for the events, map the stream of data to just the checked value
var showVariableObs = Rx.Observable.fromEvent(checkShowVariable, 'change')
                        .map(e => e.target.checked)
                        .startWith(checkShowVariable.checked);
var showValueObs    = Rx.Observable.fromEvent(checkShowValue, 'change')
                        .map(e => e.target.checked)
                        .startWith(checkShowValue.checked);

// An Observable is an object that represents a stream of async event data

// Merge the two observables and specify a combination function
var combinedObs = Rx.Observable.combineLatest(showVariableObs, 
                                              showValueObs, 
                                              (showVar, showVal) => showVar && showVal);

// subscribe to the combined observable to activate it and perform the "side effect"
combinedObs.subscribe(shouldEnable => {
  if (shouldEnable)
    checkShowOperator.removeAttribute("disabled");    
  else
    checkShowOperator.setAttribute("disabled",true);    
});

code is here

What have we gained?

  • Relationship expressed only once (combineLatest)
  • Composition of events possible
  • More declarative approach
var combinedObs = Rx.Observable.combineLatest(showVariableObs, 
                                              showValueObs, 
                                              (showVar, showVal) => showVar && showVal);

What is this Observable again?

"Object that can be observed by an Observer"

Something that:

  • will produce values in the future
  • can be chained together with other Observables (to form "pipelines")
  • has a subscribe method. Values will be pushed to anything that subscribes

Observables are push based!

  • Nothing "happens" when the Observable is created
  • Only the call to subscribe activates the originating event subscription
  • subscribe returns a Disposable, used to (optionally) unsubscribe and release the underlying resources

subscribe(

onNext, // next element

onError, // canceled with error (~ exception)

onCompleted // completed (only applies to some sources)

) // -> returns a Disposable

This is just the beginning

The real power of RxJS is complex interactions of async things

Promises

  • Great for async operations
  • Can handle errors
  • Become cumbersome to handle when you have a lot of them

Example: Autocomplete

  • Search field should have autocomplete (wikipedia articles)
  • After the 2nd character, send an XHR to the suggestion API for every keystroke
  • Throttle results
  • Results may arrive out of order
function searchWikipedia (term) {
    return $.ajax({url: 'http://en.wikipedia.org/w/api.php',
      dataType: 'jsonp', data: {
        action: 'opensearch',
        format: 'json',
        search: global.encodeURI(term)
      }}).promise();
  }

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

// Get all distinct key up events from the input and only fire if long enough and distinct
var keyup = Rx.Observable.fromEvent($input, 'keyup')
  .map(function (e) {
    return e.target.value; // Project the text from the input
  })
  .filter(function (text) {
    return text.length > 2; // Only if the text is longer than 2 characters
  })
  .debounce(750 /* Pause for 750ms */ )
  .distinctUntilChanged(); // Only if the value has changed

var searcher = keyup.flatMapLatest(searchWikipedia);

var subscription = searcher.subscribe(
// ...
);

flatMap

flatMapLatest

Observable operators hide state

  • E.g. debounce needs to keep some state
  • Nicely hidden from consumer

Observables are testable

it("should be able to complete", function() {
        var invoked = 0;
        xs = scheduler.createHotObservable(
            onNext(180, 1),
            onNext(210, 2),
            onNext(240, 3),
            onNext(290, 4),
            onNext(350, 5),
            onCompleted(400)
        );

        results = scheduler.startWithCreate(function () {
            return xs.map(function (x) {
                invoked++;
                return x + 1;
            });
        });

        expect(results.messages).toHaveEqualElements(
            onNext(210, 3),
            onNext(240, 4),
            onNext(290, 5),
            onNext(350, 6),
            onCompleted(400)
        );

        expect(xs.subscriptions).toHaveEqualElements(subscribe(200, 400));

        expect(invoked).toEqual(4);
    });

Sounds cool?

  • Reactive programming is very powerful
  • Learning curve is a little bumpy in the beginning
  • Can be introduced into existing code bases in only a few places at first where it makes most sense
  • But even whole app/library designs based on reactive are possible (compare Cycle JS)

Further recommended reading

Thank you!

 

 

 

follow me on twitter: @danyx23

Reactive Programming

By Daniel Bachler

Reactive Programming

  • 2,293