Question ?
What can send data/signals to you in an application ?
Question ?
What can send data/signals to you in an application ?
Possible answers
Actually, synchronous data are particular cases.
Incoming data and signals can
Observables are an abstraction of all these.
| 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
How do you get daily news about a given topic ?
If you've subscribed, when does it end ?
So, to put words on it:
inputEventsObservable
.pluck('target', 'value')
.map(term => term.trim())
.debounceTime(200)
.distinctUntilChanged()
.switchMap(term => term.length > 2 ? getPlaces({input: term}) : Rx.Observable.of([]))
.subscribe(showResults)
;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.
From an Observable
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
// 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
// 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
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-
http://www.webpackbin.com/EJkzVj8X-
obs.subscribe({
next(v) {},
complete() {},
error(e) {}
});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
Cold: the subscriber handler
Hot: the subscriber handler
Real life example:
A subject is an observable and an observer:
What does it imply ?
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.
Remember what we said about wrappers of synchronous and asynchronous collections ...
"Think of Rx as Lodash for events"
(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 endsThere is a common way to represent reactive data streams: marble diagrams.
Visualization of collection's items through time and their transformation/displacement.
An operator does classical operations over values and time, or even observables.
A lot of the classical data operators are there to work on array collections.
Example: trimming a search term
Remember the autocompletion example
The combination operators can work with multiple observables.
Example: you may combine multiple sources of results for displaying your application events
The partition operator can generate an array of two observables.
Example: some binary test on incoming data
Merge incoming observables
Can someone imagine the difference with a switch operator ?
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.
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.
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 ?
Every 70ms, sample.
Accept then ignore during 50ms.
Schedule sampling in 50ms when not already scheduled.
Schedule sampling in 50ms then cancel and reschedule.
Exercises III
Create multiple observables emitting random values
Merge them
Partition them with some predicate
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)
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
Listen to clicks on a button and map it to a request so that
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
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
a) Transform some source with a function which throws in some condition
b) Implement retrial a given amount of times