Introduction to
Functional Reactive Programming
with RxJS
Question ?
What can send data/signals to you in an application ?
Question ?
What can send data/signals to you in an application ?
Possible answers
- DOM events
- Ajax (via ready state change events)
- Web sockets, server sent events
- Any connection to anything...
Actually, synchronous data are particular cases.
Incoming data and signals can
- be listened to
- happen a finite or infinite number of times
- complete, throw errors
- imply specific procedures for setup and teardown
- be merged, partitioned
- be deferred, ignored
- be emitted only when asked
- be emitted anyway
Observables are an abstraction of all these.
Observables
The very common comparison
| Single | Multiple | |
|---|---|---|
| Pull | Function | Iterator |
| Push | Promise | Observable |
I pull one value synchronously
let value = "You pull me";I pull multiple values synchronously
let values = ["You", "pull", "me"];Lodash can wrap a synchronous collection and expose operators
Reactive programming is about letting values get pushed to you while you describe how to react to them
One value gets pushed asynchronously
getSentenceAsync()
.then((value) => {
// "I get pushed to you afterwards"
})
;Multiple values get pushed asynchronously
getWordsObservable()
.subscribe((value) => {
// "I", "get", "pushed", "to", "you", "afterwards"
})
;
RxJS can wrap an asynchronous collection and expose operators
Example of Observable
How do you get daily news about a given topic ?
- You go get it from the source
- You find articles on the web
- You've subscribed to newsletters
If you've subscribed, when does it end ?
- Never
- The topic is over
- When they loose their database, the servers burn
- When you unsubscribe
So, to put words on it:
- To get the news you expect (the Observable)
- You give your email address as the way to get notified (the Observer)
- To a service that expects your email to register you (the Subscriber)
- When subscribing you get an email with a link to unsubscribe (Subscription)
The very common demo: autocomplete
inputEventsObservable
.pluck('target', 'value')
.map(term => term.trim())
.debounceTime(200)
.distinctUntilChanged()
.switchMap(term => term.length > 2 ? getPlaces({input: term}) : Rx.Observable.of([]))
.subscribe(showResults)
;The very common demo: Autocomplete
- Extract term ("pluck" = "ceuillir" in french)
- Trim
- Debounce
- Ignore same term
- Search if term is long enough
- Display
inputEventsObservable
.pluck('target', 'value')
.map(term => term.trim())
.debounceTime(200)
.distinctUntilChanged()
.switchMap(term => term.length > 2 ? getPlaces({input: term}) : Rx.Observable.of([]))
.subscribe(showResults)
;Someone asked me why not filtering by term length before using the operator switchMap.
Well, switchMap is switching to the observable you decide to create from the incoming data. We want it to switch to an empty array of results when the term gets too short. Filtering first disables that.
The example can be complexified a bit to be debounced in the switchMap to enable switching to this empty array of results instantly, when the term gets too short or when scheduling a new request.
Generate Observables from common objects
From an Observable
Rx.Observable.from
Rx.Observable.from([0, 1, 2, 3]).subscribe((v) => {
/* 0, 1, 2, 3 */
});Rx.Observable.from(otherObservable);From an Array
function* gen(i) {
while (true) yield i *= 2;
}
Rx.Observable.from(gen(1)).take(4).subscribe((v) => {
/* 2, 4, 8, 16 */
});From an iterator
Rx.Observable.from(Promise.resolve(1)).subscribe((v) => {
/* 1 */
});From a Promise (at least Thenable)
and everything with an iterator factory implemented on it's [Symbol.iterator] property
From event sources
From event emitters
// DOM nodes
Rx.Observable.fromEvent(document, 'click');
// DOM node lists
Rx.Observable.fromEvent(document.childNodes, 'click');
// jQuery like collections
Rx.Observable.fromEvent($(document), 'click');
// standard event emitters
let ee = new EventEmitter();
setInterval(() => ee.emit('tick'), 1000);
Rx.Observable.fromEvent(ee, 'tick');From a callback style function
From functions
// Functions with callback as last argument
let callbackStyleFunction = (input, cb) => { cb(input * 2); };
let getObservable = Rx.Observable.bindCallback(callbackStyleFunction);
getObservable(1).subscribe((v) => {
/* 2 */
});// Same for NodeJS callback style functions
let nodeCallbackStyleFunction = (input, cb) => { cb(null, input * 2); };
let getObservable = Rx.Observable.bindNodeCallback(nodeCallbackStyleFunction);
getObservable(1).subscribe((v) => {
/* 2 */
});From a node callback style function
Observable / Subscriber relation
Observable / Subscriber relation
- Basically, an observable is built from a function that manipulates any potential subscriber
- And returns the way to handle unsubscription
const obs = Rx.Observable.create((subscriber) => {
let timers = [
setTimeout(() => { subscriber.next(0); }, 1000), // obs will notify 0 after 1 second
setTimeout(() => { subscriber.complete(); }, 2000), // obs will complete after 2 seconds
setTimeout(() => { subscriber.error(1) }, 3000) // obs won't fail cause completed before
];
return () => {
timers.forEach(clearTimeout);
};
});http://www.webpackbin.com/EJkzVj8X-
Observable / Subscriber relation
http://www.webpackbin.com/EJkzVj8X-
obs.subscribe({
next(v) {},
complete() {},
error(e) {}
});- To subscribe, you use the extraction operator subscribe and pass an object matching the observer interface
Exercise I: stability
Implement a function that takes an event emitter and returns an observable
Ensure it can unsubscribe
Same for a promise or a callback style function
Observable temperature
Hot / cold observables
Cold: the subscriber handler
- starts running on subscription
- runs once per subscription
Hot: the subscriber handler
- starts running on creation
- runs once for all subscriptions
Real life example:
- Cold: a movie you buy, it is played when you start it, someone else can buy the same and start it when he wants too
- Hot: a performance, it is done once, if you arrive late, you miss the beginning
Hot / cold observables
Observable types
Subject
A subject is an observable and an observer:
- It's an observable so you can subscribe to it
- It's an observer so you can push values
What does it imply ?
- It can be a medium to connect two parts of code that have to be decoupled
- It's a hot observable since when we push values to it, we do not wait for any subscription, simple Subjects won't re emit those values
- There are other kinds of subjects that can replay the values that were pushed before subscribing (ReplaySubject)
Exercise II: decoupling
Have an observable S which emits data then subscribe to it to display them in a list
From an input element, get an observable V which emits data
Getting data is decoupled from consuming them. Now connect them.
Functional programming with Rxjs
Remember what we said about wrappers of synchronous and asynchronous collections ...
RxJS
The very common comparison
"Think of Rx as Lodash for events"
- Both let you wrap a collection
- The wrapper exposes operators
- With Lodash, the wrapped collection is synchronous and is finite
- With RxJS, the wrapped collection can be asynchronous and can be infinite
(Let's say the events are numbers)
I write a synchronous monad with Lodash
let numbers = [1, 2, 3, 4];
let squaredOdds = _(numbers)
.filter((v) => v % 2) // keep odd numbers
.map((v) => v * v)
.value()
;
// [1, 9]I write an observable from values and react to them
let numbersObservable = getObservableFrom([1, 2, 3, 4]);
numbersObservable
.filter((v) => v % 2) // keep odd numbers
.map((v) => v * v)
.subscribe((squareOdds) => {
// 1, 9
})
;I may not know when it ends, if it does
let secondObservable = observeTimeEverySecond();
let subscriptionToSeconds = secondObservable
.scan((count) => count + 1, 0)
.subscribe((seconds) => {
// 1, 2, 3 ...
})
;You can unsubscribe, but it will never end
subscriptionToSeconds.unsubcribe();
// time never endsMarble diagrams
There is a common way to represent reactive data streams: marble diagrams.
Marble diagrams
Visualization of collection's items through time and their transformation/displacement.

Operators
An operator does classical operations over values and time, or even observables.
- Map/filter/merge/group value flows
- Defer flows based on conditions, other observables (dynamic durations, ...)
Operators for value observables
map, filter ...
A lot of the classical data operators are there to work on array collections.

Example: trimming a search term
Remember the autocompletion example
merge
The combination operators can work with multiple observables.

Example: you may combine multiple sources of results for displaying your application events
Partition
The partition operator can generate an array of two observables.
Example: some binary test on incoming data

Operators for Observables of Observables
mergeAll
Merge incoming observables

Can someone imagine the difference with a switch operator ?
Switch
The switching operators can switch to the newly started observable.
Notice that unsubscribing the observable we give up while switching, calls the implemented unsubcription process of the previous one.

combineLatest
Some combination operators emit combined values of combined observables if all of them has emitted at least once
That's the case of displays that has no meaning when some data is missing, but should react to every change.

GroupBy
The grouping operators can generate multiple observable based on a predicate.
Example: you may have a source of events that is already a merge of heterogeneous events you want to separate before subscribing.

A real life example of groupBy
You may subscribe to an undetermined set of newsletters and group them by topic.
You don't know the topics at first, you may only provide the criteria. First time a newsletter is created for a given topic, it will create a new stream of newsletters for this topic.
That's what groupBy does.
-> What would you have written with vanilla JS to implement this ?
Time operators
Every 70ms, sample.

Sample
Accept then ignore during 50ms.


Throttle & Audit
Schedule sampling in 50ms when not already scheduled.
Schedule sampling in 50ms then cancel and reschedule.

Debounce
Exercises III
Create multiple observables emitting random values
Merge them
Partition them with some predicate
Schedulers
Schedulers
- Schedulers are used to schedule when a subscription starts and when data are emitted.
- It lets place a task in another event loop, letting multiple tasks work concurrently.
- Existing ones
- immediate (synchronous, recursive) - default
- queue (synchronous, trampoline)
- asap (next tick)
- async (periodically)
- animationFrame (next animation frame)
Notifications
Notifications
-
Dematerialized observables transport data.
-
Materialized observables transport notifications that are objects determining themselves whether they are data, completions or errors.
- You can go forth and back from the former to the latter with materialize and dematerialize operators.
Publishing transform chains
- Subscribing multiple times to a transformed observable makes the transform chain being traversed for each subscription: it actually subscribes to the source, before any transformation.
-
You can publish this transform chain with the operator publish. This gives a Connectable that is not connected to the source.
It will wait for being connected with the operator connect before calling subscribers.
- To implement this, a Connectable holds a Subject internally. The type of Subject depends on the operator used:
publish, publishReplay, publishBehavior, publishLast.
You may even provide your own Subject or a factory of it, with the operator multicast.
Transform chains are not new sources
- A Connectable can connect automatically when there is at least one subscription and unsubscribe when all left, with the operator refCount.
- .publish().refCount() = .share(): the transform chain is traversed once if there is at least one subscription. If you want another type of Subject, don't use share.
Sharing transform chains
Useful links
?
Representation proposal
for the exercises
Source
observable




Transformed
observable
Operator
Subscription
Subject (observer and observable)


A source observable with two successive transformations

A shared observable (the new source for subscribers)

- A transformed observable
- which is transformed and subscribed twice separately

- A transformed observable
- which is shared
- then transformed and subscribed twice separately
Exercises
Ex 1: warm up
function fromArray<T>(v: T[]): Observable<T> {
/* implement this */
}b) Implement a function which takes a synchronous array and transform it to an observable
function search(term: string): Observable<string[]> {
/* implement this */
}c) Implement a function takes a string and provide an array of strings
a) Implement a subscription which simply logs everything that goes out of a given an observable
Ex 2: Mapping to asynchronous processes
Listen to clicks on a button and map it to a request so that
- a) no new request starts till the previous one ends
- b) all requests start and all results are used
- c) requests are queued
- d) the previous request is cancelled when a new one starts

Ex 3: Using a subject
a) Map a subject to requests, then merge all requests
b) Contract it to one operator use
c) Subscribe twice and compare with and without sharing

Ex 4: Reimplement autocomplete
a) Listen to input events from a DOM input
b) Extract value of input events from a DOM input
c) Trim it to get a search term
d) Ignore events if the term did not change
e) Debounce based on the term length (no delay if term is too short)
f) Call the search function of first exercise and cancel it when a new request is required
g) (optional) Display results

Ex 5: retry a process
a) Transform some source with a function which throws in some condition
b) Implement retrial a given amount of times

Thanks
Introduction to Functional Reactive Programming with RxJS
By Alexis Tondelier
Introduction to Functional Reactive Programming with RxJS
- 895