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
- Introduction Article by André Staltz
- RxJS Getting Started
- ReactiveX Docs
- Rx Roans - Zen Koans for reactive padawans
- Rx Marbles - Visualisation of various operators
- Visualizing Hot and Cold Observables by Jared Forsyth
Thank you!
Reactive Programming
By Daniel Bachler
Reactive Programming
- 2,293