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?

Observables & RxJS

By Maciej

Observables & RxJS

  • 863