Observables & RxJS
Maciej Stasiełuk, 20.04.18
Observables
- ECMAScript proposal - Stage 1
Object.observe- Reprezentuje ideę przyszłego zbioru wartości lub wydarzeń (a.k.a. strumień danych/wydarzeń)
- Dalej jest w trakcie procesu standaryzacji, ale RxJS jest jedną z wzorcowych implementacji, więc można tego używać już dziś
Pojedyncza wartość | Wiele wartości | |
Synchronicznie |
|
|
Asynchronicznie |
|
const value = 42;
console.log(value);
const values = [4, 8, 15, 16];
values.forEach(value => {
console.log(value);
})
const asyncValue =
Promise.resolve(42);
asyncValue.then(value => {
console.log(value);
});
const values$ =
Observable.of(4, 8, 15, 16);
values$.subscribe(value => {
console.log(value);
});
Zastosowania
- Reagowanie na akcje użytkownika:
kliknięcia, ruch myszki, klawiatura itp. - Otrzymywanie i reagowanie na dane, również w czasie rzeczywistym, np. po web socketach
- Zdarzenia wykonywane po czasie np. setTimeout, setInterval
- Wszystko co asynchronicznie zwraca od 0 do nieskończonej ilości wartości
Słowniczek
-
Observable - reprezentuje sekwencję wartości w czasie, która może być obserwowana
-
Observer - obiekt który otrzymuje dane z Observable
-
Subscription - wynik subskrybowania, służy głównie do zamykania/zatrzymywania subskrypcji
-
Operators - zbiór metod dla Observable, najczęściej zwracające nowe Observable (np. map, filter)
- Subject - to samo co Observable, ale nieco bardziej zaawansowane :)
Observable
- Reprezentuje sekwencje wartości w czasie, które są przez nią emitowane
- Można ją zasubskrybować aby otrzymywać te wartości
-
Leniwe - kod wykona się i zaczynie produkować wartości dopiero kiedy ktoś słucha (czyli subskrybuje) *
- Może wyemitować błąd zamiast wartości (jak promise) lub sygnał "zakończenia" strumienia. W obu wypadkach taki observable jest uważany za zakończony i nie wyśle więcej żadnej wartości
Tworzenie obserwowanych
Do wyboru do koloru!
const clock$ = new Observable(observer => {
setInterval(() => {
observer.next('tick');
}, 1000);
});
const colors$ = Observable.of('red', 'green', 'blue');
const colors$ = Observable.from(['red', 'green', 'blue']);
RxJS dodaje dużo nowych metod jak np:
- fromEvent
- fromPromise
- bindCallback
- timer
Nasłuchiwanie na zmiany
.subscribe() przyjmuje dwa rodzaje argumentów:
observable$.subscribe({
next: value => console.log('Nowa wartość:', value),
error: err => console.error(err),
complete: () => console.log('Koniec nadawania'),
});
1. Listę callbacków
2. Obiekt typu Observer
observable$.subscribe(
value => console.log('Nowa wartość:', value),
err => console.error(err),
() => console.log('Koniec nadawania')
);
Observable nie są takie jak myślisz!
Lazy computation i nie zawsze asynchroniczna,
czyli bardziej jak funkcja niż promise!
const hello$ = new Observable(observer => {
console.log('2. Hello');
observer.next('3. Lorem');
observer.next('4. Ipsum');
});
console.log('1. Before');
hello$.subscribe(x => console.log(x));
console.log('5. After');
// Output:
// 1. Before
// 2. Hello
// 3. Lorem
// 4. Ipsum
// 5. After
Hot vs Cold
Observables domyślnie są zimne:
- każda subskrypcja powoduje wywołanie konstruktora
- odsubskrybowanie skutkuje zamknięciem tej instancji obserwowanej
Mogą być też gorące:
- Przestają być leniwe*
- Mogą produkować wartości nawet kiedy nikt nie słucha*
- Wywołanie subscribe wiele razy nie skutkuje wywołaniem konstruktora wielokrotnie*
- Mogą współdzielić przesyłane wydarzenia*
Observer
Specjalny obiekt służący do "konsumowania" wartości które dostarcza Observable.
Najprościej mówiąc jest to obiekt z trzema metodami - callbackami dla każdego rodzaju notyfikacji którą może dostarczyć Observable:
const observer = {
next: value => console.log('Nowa wartość:', value),
error: err => console.error(err),
complete: () => console.log('Koniec nadawania'),
};
Subskrypcja
Specjalny obiekt reprezentujący zasób, najczęściej konkretne wykonanie danego Observable.
Obiekt praktycznie ma tylko jedną metodę: unsubscribe()
const clock$ = new Observable(observer => {
const intervalId = setInterval(() => {
observer.next('tick');
}, 1000);
return () => clearInterval(intervalId);
});
const subscription = clock$.subscribe(val => console.log(val));
subscription.unsubscribe();
Demo
RxJS
Część większej rodziny Rx* jak np. RxJava, Rx.NET, RxScala...
Oryginalnie stworzona i rozwijana przez Microsoft (do v.4), obecnie (od v.5) przepisana i rozwijana m.in przez Google i Netflixa.
Zawiera implementację Observable i innych typów, ale przede wszystkim posiada bogatą kolekcję Operatorów.
Lodash dla observable
Operators
Operatory służą do komponowania obserwowanych i łatwego deklaratywnego zarządzania asynchronicznym kodem.
Najczęściej występują w formie metod dostępnych na instancji obiektu Observable.
Użycie operatora nigdy nie zmienia (nie mutuje) oryginalnego Observable ani wartości jakie przez niego przechodzą. Zamiast tego zawsze tworzy nowy Observable na bazie istniejącego.
Przykładowe operatory
const numbers$ = Observable.from([1, 2, 3, 4, 5]);
const doubleNumbers$ = numbers$.map(num => num * 2);
doubleNumbers$.subscribe(num => console.log(num));
// Output:
// 2
// 4
// 6
// 8
// 10
const numbers$ = Observable.from([1, 2, 3, 4, 5]);
const smallNumbers$ = numbers$.filter(num => num < 4);
smallNumbers$.subscribe(num => console.log(num));
// Output:
// 1
// 2
// 3
const numbers$ = Observable.from([1, 2, 3, 4, 5]);
numbers$
.filter(num => num > 2)
.map(num => num * 2)
.subscribe(num => console.log(num));
// Output:
// 6
// 8
// 10
Własne operatory
const echo = (input$) => {
return new Observable(observer => {
input$.subscribe({
next: val => {
observer.next(val);
observer.next(val);
},
error: err => observer.error(err),
complete: () => observer.complete()
});
});
}
const numbers$ = Observable.from([1, 2, 3, 4, 5]);
const echoNumbers$ = echo(numbers$);
echoNumbers$.subscribe(num => console.log(num));
// Output:
// 1
// 1
// 2
// 2
// 3
// 3
// 4
// 4
// 5
// 5
Marble diagrams
map

filter

merge

RxMarbles.com
Operators...
Creation Operators
ajax
bindCallback
bindNodeCallback
create
defer
empty
from
fromEvent
fromEventPattern
fromPromise
generate
interval
never
of
repeat
repeatWhen
range
throw
timer
Transformation Operators
buffer
bufferCount
bufferTime
bufferToggle
bufferWhen
concatMap
concatMapTo
exhaustMap
exhaustMap
expand
groupBy
map
mapTo
mergeMap
mergeMapTo
mergeScan
pairwise
partition
pluck
scan
switchMap
switchMapTo
window
windowCount
windowTime
windowToggle
windowWhen
Filtering Operators
debounce
debounceTime
distinct
distinctKey
distinctUntilChanged
distinctUntilKeyChanged
elementAt
filter
first
ignoreElements
audit
auditTime
last
sample
sampleTime
single
skip
skipLast
skipUntil
skipWhile
take
takeLast
takeUntil
takeWhile
throttle
throttleTime
Combination Operators
combineAll
combineLatest
concat
concatAll
exhaust
forkJoin
merge
mergeAll
race
startWith
switch
withLatestFrom
zip
zipAll
Multicasting Operators
cache
multicast
publish
publishBehavior
publishLast
publishReplay
share
Error Handling Operators
catch
retry
retryWhen
Utility Operators
do
delay
delayWhen
dematerialize
finally
let
materialize
observeOn
subscribeOn
timeInterval
timestamp
timeout
timeoutWith
toArray
toPromise
Conditional and Boolean Operators
defaultIfEmpty
every
find
findIndex
isEmpty
Mathematical and Aggregate Operators
count
max
min
reduce


























Demo
Pytania?
Ciekawe linki
- RxJS manual
- The introduction to Reactive Programming you've been missing
- learnrxjs.io
- reactivex.io/learnrx (dla starszej wersji, ale warto)
- RxJS Marbles
- Rx Visualizer
- reactive.how
- Cycle.js
Observables & RxJS
By Maciej
Observables & RxJS
- 952