Reactive JavaScript
@MichalZalecki
woumedia.com
reactive
\rē-ˈak-tiv\
done in response to a problem or situation
‒ merriam-webster.com
reactive programming
programming paradigm oriented around data flows and the propagation of change
‒ wikipedia.org/wiki/Reactive_programming
...but why should I care?
RP is a problem solver
and because you
have problem
because you we
have problem
async is hard
everything on front-end is async
...because user is async
Which problems RP solves?
- data manipulation
- events handling
- errors handling
- callback hell
- promise hell
- web workers
- sockets
- managing updates
- race conditions
- complex state
- state dependencies
- memory leaks
and more
but... how?
Observer pattern
Iterator pattern
Functional programming
+
ReactiveX
my very scientific chart
async awesomeness
promises
await/async
RxJS
function usersLanguages(username) {
return fetch(`https://api.github.com/users/${username}`)
.then(response => response.json())
.then(data => fetch(data.repos_url))
.then(response => response.json())
.then(repos => Object.keys(
repos.map(repo => repo.language)
.filter(lang => !!lang)
.reduce((akk, lang) => ({ ...akk, [lang]: null }), {})
));
}
usersLanguages("MichalZalecki").then(::console.log);
// ["JavaScript", "CoffeeScript", "CSS", "HTML", "Ruby", "Java"]
async function usersLanguages(username) {
const user = await fetch(`https://api.github.com/users/${username}`);
const userJSON = await user.json();
const userRepos = await fetch(userJSON.repos_url);
const userReposJSON = await userRepos.json();
return Object.keys(
userReposJSON.map(repo => repo.language)
.filter(lang => !!lang)
.reduce((akk, lang) => ({ ...akk, [lang]: null }), {})
);
}
usersLanguages("MichalZalecki").then(::console.log);
// ["JavaScript", "CoffeeScript", "CSS", "HTML", "Ruby", "Java"]
function usersLanguages(username) {
return Rx.DOM.ajax(`https://api.github.com/users/${username}`)
.flatMap(({response}) => Rx.DOM.ajax(JSON.parse(response).repos_url))
.flatMap(({response}) => JSON.parse(response))
.map(repo => repo.language)
.filter(lang => !!lang)
.distinct();
}
usersLanguages("MichalZalecki").subscribe(::console.log);
// ["JavaScript", "CoffeeScript", "CSS", "HTML", "Ruby", "Java"]
promises
await/async
RxJS
callbacks
async awesomeness
Observer pattern
Iterator pattern
Functional programming
+
ReactiveX
Single | Multiple | |
---|---|---|
Pull | Object | Iterable |
Push | Promise | Observable |
import Rx from "rxjs";
const obs$ = Rx.Observable.create(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
});
obs$.subscribe(::console.log);
// 1 2 3
import Rx from "rxjs";
const obs$ = Rx.Observable.create(observer => {
observer.next(1);
observer.next(2);
observer.error("Boom!");
observer.next(3);
});
obs$.subscribe(::console.log, ::console.error);
// 1 2 "Boom!"
import Rx from "rxjs";
const obs$ = Rx.Observable.create(observer => {
observer.next(1);
observer.next(2);
observer.error("Boom!");
observer.next(3);
});
const obs2$ = Rx.Observable.of(100)
Rx.Observable.onErrorResumeNext(obs$, obs2$)
.subscribe(::console.log, ::console.error);
// 1 2 100
import Rx from "rxjs";
const obs$ = Rx.Observable.create(observer => {
observer.next(1);
observer.next(2);
observer.error("Boom!");
observer.next(3);
});
const obs2$ = Rx.Observable.of(100);
obs$.catch(err => obs2$)
.subscribe(::console.log, ::console.error);
// 1 2 100
import Rx from "rxjs";
const obs$ = Rx.Observable.create(observer => {
observer.next(1);
observer.next(2);
observer.complete("Done!");
observer.next(3);
});
obs$.subscribe(
::console.log,
::console.error,
::console.info
);
// 1 2 undefined
import Rx from "rxjs";
const obs$ = Rx.Observable.create(observer => {
let i = 1;
const tid = setInterval(() => {
observer.next(i++);
}, 1000);
return () => clearInterval(tid);
});
const subscription = obs$.subscribe(::console.log);
setTimeout(() => subscription.unsubscribe(), 3000);
// 1..2..3
import Rx from "rxjs";
const obs$ = Rx.Observable
.create(function subscribe(observer) {
return function unsubscribe() {};
});
import Rx from "rxjs";
const obs$ = Rx.Observable.create(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
});
console.log("before");
obs$.subscribe(::console.log);
console.log("after");
// "before" 1 2 3 "after"
import Rx from "rxjs";
const obs$ = Rx.Observable.interval(1000);
obs$.subscribe(x => console.log(`A: ${x}`));
setTimeout(() => {
obs$.subscribe(x => console.log(`B: ${x}`));
}, 3000);
// A: 0
// A: 1
// A: 2
// B: 0
// A: 3
// B: 1
import Rx from "rxjs";
const obs$ = Rx.Observable.interval(1000).share();
obs$.subscribe(x => console.log(`A: ${x}`));
setTimeout(() => {
obs$.subscribe(x => console.log(`B: ${x}`));
}, 3000);
// A: 0
// A: 1
// A: 2
// B: 2
// A: 3
// B: 3
Observer pattern
Iterator pattern
Functional programming
+
ReactiveX
const iterable = [1, "3", "xyz", 6];
iterable
.forEach(x => console.log(x));
// 1 "3" "xyz", 6
import Rx from "rxjs";
const iterable = [1, "3", "xyz", 6];
Rx.Observable.from(iterable)
.subscribe(x => console.log(x));
// 1 "3" "xyz", 6
const iterable = [1, "3", "xyz", 6];
const result = iterable
.map(x => parseInt(x))
.filter(x => !!x)
.reduce((akk, x) => akk+x, 0);
console.log(result);
// 10
import Rx from "rxjs";
const iterable = [1, "3", "xyz", 6];
const obs$ = Rx.Observable.from(iterable)
.map(x => parseInt(x))
.filter(x => !!x)
.reduce((akk, x) => akk+x, 0);
obs$.subscribe(::console.log);
// 10
import Rx from "rxjs";
const iterable = [1, "3", "xyz", 6];
const obs$ = Rx.Observable.from(iterable)
.map(x => parseInt(x))
.filter(x => !!x)
.scan((akk, x) => akk+x, 0);
obs$.subscribe(::console.log);
// 1 3 10
import Rx from "rxjs";
const iterable = [1, "3", "xyz", 6];
const obs$ = Rx.Observable.from(iterable)
.map(x => parseInt(x))
.filter(x => !!x)
.do(::console.info)
.scan((akk, x) => akk+x, 0);
obs$.subscribe(::console.log);
// 1 1 3 4 6 10
import Rx from "rxjs";
const iterable = new Set([1, 1, "3", "xyz", 6]);
const obs$ = Rx.Observable.from(iterable)
.map(x => parseInt(x))
.filter(x => !!x)
.scan((akk, x) => akk+x, 0);
obs$.subscribe(::console.log);
// 1 3 10
import Rx from "rxjs";
const promise = Promise.resolve("meet.js");
const obs$ = Rx.Observable.fromPromise(promise);
obs$.subscribe(::console.log);
// "meet.js"
import Rx from "rxjs";
const obs$ = Rx.Observable
.fromEvent(document, "mousemove");
obs$.subscribe(::console.log);
// MouseEvent..MouseEvent..MouseEvent..
import Rx from "rxjs";
import "rxjs/add/observable/dom/webSocket";
const obs$ = Rx.Observable
.webSocket("ws://localhost:8081");
obs$.subscribe(::console.log);
// message..message..message...
Observer pattern
Iterator pattern
Functional programming
+
ReactiveX
operators...
...but you don't need them all
seriously
The most useful*
- map
- filter
- flatMap
- concatMap
- reduce
- scan
- do
- retry
- catch
- zip
- groupBy
* speaking from my experience
- take
- skip
- publish
- share
- shareReplay (pushReplay + refCount)
- withLatestFrom
- debounce/debounceTime
- buffer
- delay
- pluck
- distinct/distinctUntilChanged
"Rx is lodash or underscore for async"
Cycle.js
import Cycle from "@cycle/core";
import {makeDOMDriver} from "@cycle/dom";
function main(sources) {
const actions = intent(sources);
const state$ = model(actions);
const sinks = {
DOM: view(state$),
};
return sinks;
}
const drivers = {
DOM: makeDOMDriver("#root"),
};
Cycle.run(main, drivers);
Reactive JavaScript
By Michał Załęcki
Reactive JavaScript
- 4,134