Rx.js
making async programming simpler
About me
Mostovenko Alexander
Python developer at
Writing mostly in python and coffeescript.
Love FP stuff.
Traveling, snowboarding, cycling.
https://twitter.com/MostovenkoA
https://github.com/AlexMost
Why Rx.js and not another FRP lib?
● Implemented in a lot of languages
(.NET, Java, Ruby, Python, Clojure, Scala,
Haskell, Objective-C).
● Has a huge amount of docs and examples.
● Why not?
Asynchronous code
Async code in javascript
● Some computation with delayed result.
(data fetch, animations, DOM operations)
● Reacting to events (event handlers)
Sync vs Async dataflow
Synchronous dataflow
Synchronous dataflow
In
Synchronous dataflow
In
1
Synchronous dataflow
In
1
2
Synchronous dataflow
In
1
2
...
Synchronous dataflow
In
1
2
Out
...
Asynchronous dataflow
time
Actions
Asynchronous dataflow (autocomplete)
time
Actions
keyup
Asynchronous dataflow (autocomplete)
time
Actions
keyup
per 2 seconds
Asynchronous dataflow (autocomplete)
time
Actions
keyup
per 2 seconds
ajax request
Asynchronous dataflow (autocomplete)
time
Actions
keyup
per 2 seconds
ajax request
ajax response
Asynchronous dataflow (autocomplete)
time
Actions
keyup
per 2 seconds
ajax request
ajax response
show suggest
Asynchronous dataflow (autocomplete)
time
Actions
keyup
per 2 seconds
ajax request
ajax response
show suggest
Asynchronous dataflow (autocomplete)
time
Actions
keyup
per 2 seconds
ajax request
ajax response
show suggest
Ooooops
Problems with async code
1. How to manage async flow in code?
2. How to handle errors?
3. How to compose?
Callbacks
● Event notification
● Manage control flow
Callbacks
● Event notification - Yes
● Manage control flow - Nooooooooooo
Callback hell
async_func1(function(err, data){
async_func2(function(err, data){
async_func3(function(err, data){
async_func4(function(err, data){
});
});
});
});
Callback hell yeah !!!
async_func1(function(err, data){
if (!err) {
async_func2(function(err, data){
if (!err) {
async_func3(function(err, data){
if (!err) {
async_func4(function(err, data){
});
} else {
log(err);
}
});
} else {
log(err);
}
});
} else {
log(err);
}
});
Callback hell yeah !!!
async_func1(function(err, data){
if (!err) {
async_func2(function(err, data){
if (!err) {
async_func3(function(err, data){
if (!err) {
async_func4(function(err, data){
});
} else {
log(err);
}
});
} else {
log(err);
}
});
} else {
log(err);
}
});
UNREADABLE !!!
UNMANAGABLE !!!
UNTESTABLE !!!
UNCOMPOSABLE !!!
PIECE OF ….
CODE
Monad
A monad is just a monoid in the category of
endofunctors
Callback vs Black hole
some_async_func(function(err, result) {
// ... looks like we are in trap
another_wonderful_func(function(err, result){
// ... you are in trap
});
});
Callback vs Black Hole
1. No return value (undefined)
Callback vs Black Hole
1. No return value (undefined)
2. One way ticket (callback hell)
How about Promise?
Promise
var promise = new Promise((resolve, reject) => {
// ... some async staff
});
promise.then(
(result) => {},
(error) => {}
)
Promise
1. Step forward from callbacks for control flow
managing
2. Error handling
Why we should look at Observable?
1. Promise represents only single value.
2. How to cancel?
3. Lazy execution.
4. We can work with Observable as easy as with
promise.
Promise is Observable only with a single value
Observable
The Observable object represents a push
based collection.
Observable
var source = Rx.Observable.create((observer) =>
// some async operation
observer.onNext(data)
// or ..
observer.onError(data)
// or ..
observer.onCompleted(data)
);
source.subscribe(
(data) => {}
(err) => {}
)
Observlable vs promise example
var source = Rx.Observable.create((observer) => {
setTimeout(() => {
console.log("observable timeout hit");
observer.onNext(25);
}, 500);
console.log('observable started')});
source.subscribe(x => console.log(`observable value ${x}`));
Observable
> observable started
> observable timeout hit
> observable value 25
Observlable vs promise example
var promise = new Promise((resolve) => {
setTimeout(() => {
console.log("promise timeout hit");
resolve(25);
}, 500);
console.log("promise started");});
promise.then(x => console.log(`promise value ${x}`));
Promise
> promise started
> promise timeout hit
> promise value 25
Lazy execution
var source = Rx.Observable.create((observer) => {
setTimeout(() => {
console.log("observable timeout hit");
observer.onNext(25);
}, 500);
console.log('observable started')});
// source.subscribe(x => console.log(`observable value ${x}`));
>
Observable can be canceled
var source = Rx.Observable.create((observer) => {
var id = setTimeout(() => {
console.log("observable timeout hit");
observer.onNext(25);
}, 500);
console.log('observable started');
return () => {
console.log("dispose called")
clearTimeout(id);}
});
var disposable = source.subscribe(
x => console.log(`observable value ${x}`));
setTimeout(() => disposable.dispose(), 100)
> observable started
> dispose called
Asynchronous programming landscape
multiple
values
single
value
sync async
a = f(x) a = f(x).then(...)
collection = [1, 2, 3]
a = collection
.filter((v) => v > 2)
Promise
Array
Sync value
?
Asynchronous programming landscape
multiple
values
single
value
sync async
a = f(x) a = f(x).then(...)
collection = [1, 2, 3]
a = collection
.filter((v) => v > 2)
move = Rx.Observable.fromEvent(document, 'mousemove')
my_moves = move
.filter((ev) => ev.clientX > 100)
my_moves.subscribe(...)
Promise
Array
value
Observable
How to create Observable
● Observable.create
● Observable.fromEvent
● Observable.fromNodeCallback
● Observable.fromArray
● Observable.fromPromise
Drag & Drop example
What if i told you ….
That mouse move event is an array and you
can map, filter over it?
var dragTarget = document.getElementById('dragTarget');
// Get the three major events collections
var mouseup = Rx.DOM.fromEvent(dragTarget, 'mouseup');
var mousemove = Rx.DOM.fromEvent(document, 'mousemove');
var mousedown = Rx.DOM.fromEvent(dragTarget, 'mousedown');
var mousedrag = mousedown.flatMap(({offsetX, offsetY}) => {
return mousemove
.map(({clientX: x, clientY: y}) => {
return {left: x - offsetX, top: y - offsetY}})
.takeUntil(mouseup)
});
var subscription = mousedrag.subscribe((pos) => {
dragTarget.style.top = pos.top + 'px';
dragTarget.style.left = pos.left + 'px';
});
var dragTarget = document.getElementById('dragTarget');
// Get the three major events collections
var mouseup = Rx.DOM.fromEvent(dragTarget, 'mouseup');
var mousemove = Rx.DOM.fromEvent(document, 'mousemove');
var mousedown = Rx.DOM.fromEvent(dragTarget, 'mousedown');
var mousedrag = mousedown.flatMap(({offsetX, offsetY}) => {
return mousemove
.map(({clientX: x, clientY: y}) => {
return {left: x - offsetX, top: y - offsetY}})
.takeUntil(mouseup)
});
var subscription = mousedrag.subscribe((pos) => {
dragTarget.style.top = pos.top + 'px';
dragTarget.style.left = pos.left + 'px';
});
Create observable collections
from DOM events
var dragTarget = document.getElementById('dragTarget');
// Get the three major events collections
var mouseup = Rx.DOM.fromEvent(dragTarget, 'mouseup');
var mousemove = Rx.DOM.fromEvent(document, 'mousemove');
var mousedown = Rx.DOM.fromEvent(dragTarget, 'mousedown');
var mousedrag = mousedown.flatMap(({offsetX, offsetY}) => {
return mousemove
.map(({clientX: x, clientY: y}) => {
return {left: x - offsetX, top: y - offsetY}})
.takeUntil(mouseup)
});
var subscription = mousedrag.subscribe((pos) => {
dragTarget.style.top = pos.top + 'px';
dragTarget.style.left = pos.left + 'px';
});
Using power of Rx, combine
existing event streams to produce
mouse drag event collection
var dragTarget = document.getElementById('dragTarget');
// Get the three major events collections
var mouseup = Rx.DOM.fromEvent(dragTarget, 'mouseup');
var mousemove = Rx.DOM.fromEvent(document, 'mousemove');
var mousedown = Rx.DOM.fromEvent(dragTarget, 'mousedown');
var mousedrag = mousedown.flatMap(({offsetX, offsetY}) => {
return mousemove
.map(({clientX: x, clientY: y}) => {
return {left: x - offsetX, top: y - offsetY}})
.takeUntil(mouseup)
});
var subscription = mousedrag.subscribe((pos) => {
dragTarget.style.top = pos.top + 'px';
dragTarget.style.left = pos.left + 'px';
});
Subscribe on mouse drag events,
updating top and left attributes
Observable are first class events
Can be:
- passed as a parameter
- returned from a function
- assigned to a variable
Some common basic operations
map
filter
reduce
zip
merge
flatMap
...
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md
map
http://rxmarbles.com/#map
filter
http://rxmarbles.com/#filter
var mousedrag_in_area = mousedrag.filter(({top, left}) => {
return top <= 200 && left <= 200
});
var subscription = mousedrag_in_area.subscribe((pos) => {
dragTarget.style.top = pos.top + 'px';
dragTarget.style.left = pos.left + 'px';
});
let’s filter drag area
delay
http://rxmarbles.com/#delay
let’s create slow mouse drag
var slow_mousedrag = mousedrag.delay(700)
var subscription = slow_mousedrag.subscribe((pos) => {
dragTarget.style.top = pos.top + 'px';
dragTarget.style.left = pos.left + 'px';
});
reduce
http://rxmarbles.com/#reduce
let’s record our drag path
var drag_path = mousedrag
.takeUntil(mouseup)
.reduce(((a, b) => a + `${b.top} ${b.left};`), "")
drag_path.subscribe((path) => pathTarget.innerHTML = path)
flatMap
flatMap
var mousedrag = mousedown.flatMap(({offsetX, offsetY}) => {
return mousemove
.map(({clientX: x, clientY: y}) => {
return {left: x - offsetX, top: y - offsetY}})
.takeUntil(mouseup)
});
flatMap
var mousedrag = mousedown.flatMap(({offsetX, offsetY}) => {
return mousemove
.map(({clientX: x, clientY: y}) => {
return {left: x - offsetX, top: y - offsetY}})
.takeUntil(mouseup)
});
Outer stream
flatMap
var mousedrag = mousedown.flatMap(({offsetX, offsetY}) => {
return mousemove
.map(({clientX: x, clientY: y}) => {
return {left: x - offsetX, top: y - offsetY}})
.takeUntil(mouseup)
});
value as a stream
merge
http://rxmarbles.com/#merge
merge
var btnTarget = document.getElementById('okbtn');
var buttonClicks = Rx.DOM.fromEvent(btnTarget, 'click');
var move_to_100 = buttonClicks.map(() => {return {left: 100, top: 100}});
var change_position = Rx.Observable.merge(mousedrag, move_to_100)
change_position.subscribe((pos) => {
dragTarget.style.top = pos.top + 'px';
dragTarget.style.left = pos.left + 'px';
});
merge
var btnTarget = document.getElementById('okbtn');
var buttonClicks = Rx.DOM.fromEvent(btnTarget, 'click');
var move_to_100 = buttonClicks.map(() => {return {left: 100, top: 100}});
var change_position = Rx.Observable.merge(mousedrag, move_to_100)
change_position.subscribe((pos) => {
dragTarget.style.top = pos.top + 'px';
dragTarget.style.left = pos.left + 'px';
});
new stream
And a lot of other methods ….
● amb
● and
● asObservable
● average
● buffer
● bufferWithCount
● bufferWithTime
● bufferWithTimeOrCount
● catch | catchError
● combineLatest
● concat
● concatAll
● concatMap
● concatMapObserver
● connect
● includes
● controlled
● count
● debounce
● debounceWithSelector
● defaultIfEmpty
● delay
● delaySubscription
● delayWithSelector
● dematerialize
● distinct
● distinctUntilChanged
● do
● doOnNext
● doOnError
● doOnCompleted
● doWhile
● elementAt
● elementAtOrDefault
● every
● expand
● extend
● filter
● find
● findIndex
● first
● firstOrDefault
● flatMap
● flatMapObserver
● flatMapLatest
● forkJoin
● groupBy
● groupByUntil
● groupJoin
● ignoreElements
● indexOf
● isEmpty
● join
● jortSort
● jortSortUntil
● last
● lastOrDefault
● merge
● mergeAll
● min
● minBy
● multicast
● observeOn
● onErrorResumeNext
● pairwise
● partition
● pausable
● pausableBuffered
● pluck
● publish
● publishLast
● publishValue
● share
● shareReplay
● shareValue
● refCount
● reduce
● repeat
● replay
● retry
● retryWhen
● sample
● scan
● select
● selectConcat
● selectConcatObserver
● selectMany
● selectManyObserver
● selectSwitch
● sequenceEqual
● single
● singleOrDefault
● singleInstance
● skip
● skipLast
● skipLastWithTime
● skipUntil
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md
Testing with time machine
Rx.TestScheduler
var mult_stream = some_stream.debounce(100).map((n) => n * 2)
assertRxActions(
mult_stream,
[
[100, {value: 20}],
[200, {value: 40}]
],
[
[200, {value: 80}]
],
(result) => assertTrue(result)
)
var mult_stream = some_stream.debounce(100).map((n) => n * 2)
assertRxActions(
mult_stream,
[
[100, {value: 20}],
[200, {value: 40}]
],
[
[200, {value: 80}]
],
(result) => assertTrue(result)
)
stream to test
var mult_stream = some_stream.debounce(100).map((n) => n * 2)
assertRxActions(
mult_stream,
[
[100, {value: 20}],
[200, {value: 40}]
],
[
[200, {value: 80}]
],
(result) => assertTrue(result)
)
Input stream messages
var mult_stream = some_stream.debounce(100).map((n) => n * 2)
assertRxActions(
mult_stream,
[
[100, {value: 20}],
[200, {value: 40}]
],
[
[200, {value: 80}]
],
(result) => assertTrue(result)
)
time
var mult_stream = some_stream.debounce(100).map((n) => n * 2)
assertRxActions(
mult_stream,
[
[100, {value: 20}],
[200, {value: 40}]
],
[
[200, {value: 80}]
],
(result) => assertTrue(result)
)
Expected result at time
var mult_stream = some_stream.debounce(100).map((n) => n * 2)
assertRxActions(
mult_stream,
[
[100, {value: 20}],
[200, {value: 40}]
],
[
[200, {value: 80}]
],
(result) => assertTrue(result)
)
callback for assert
React ?
● Rx-React
● RxReact
● cycle-react
● React RxJS Autocomplete
● React RxJS TODO MVC
● Rx TODO MVC
● React RxJS Router
● React + RxJS + Angular 2.0 di.js TODO MVC
● React + RxJS Reactive Cube
● Real-Time with React + RxJS + Meteor
● React + RxJS Flow
● Reactive Widgets
● React RxJS Infinite Scroll
Flux ?
● Rx-Flux
● ReactiveFlux
● Thundercats.js
● Flurx
● RR
RxReact
some info - http://habrahabr.ru/post/251835/
repo with demos - https://github.com/AlexMost/RxReact
RxReact (simple click count)
Simple case
demo - http://alexmost.github.io/RxReact/hello_world/public/index.html
code - https://github.com/AlexMost/RxReact/tree/master/hello_world/hello_world
dispatchActions = (view, eventStream, store) ->
incrementClickStream = eventStream
.filter(({action}) -> action is "increment_click_count")
.do(-> store.incrementClicksCount())
Rx.Observable.merge(
incrementClickStream
# some more actions here for updating view ...
).subscribe(
-> view.setProps getViewState(store)
(err) ->
console.error? err)
RxReact (simple click count)
dispatchActions = (view, eventStream, store) ->
incrementClickStream = eventStream
.filter(({action}) -> action is "increment_click_count")
.do(-> store.incrementClicksCount())
Rx.Observable.merge(
incrementClickStream
# some more actions here for updating view ...
).subscribe(
-> view.setProps getViewState(store)
(err) ->
console.error? err)
RxReact (simple click count)
stream from view for increment and storage
modification
dispatchActions = (view, eventStream, store) ->
incrementClickStream = eventStream
.filter(({action}) -> action is "increment_click_count")
.do(-> store.incrementClicksCount())
Rx.Observable.merge(
incrementClickStream
# some more actions here for updating view ...
).subscribe(
-> view.setProps getViewState(store)
(err) ->
console.error? err)
RxReact (simple click count)
merge all streams that can modify UI
dispatchActions = (view, eventStream, store) ->
incrementClickStream = eventStream
.filter(({action}) -> action is "increment_click_count")
.do(-> store.incrementClicksCount())
Rx.Observable.merge(
incrementClickStream
# some more actions here for updating view ...
).subscribe(
-> view.setProps getViewState(store)
(err) ->
console.error? err)
RxReact (simple click count)
update UI, making setProps on view
component
RxReact (simple click count)
More complex case
1. Decrement.
2. Sync with server (per 1 second).
3. Sync only if value changed.
4. Show sync success message.
5. Hide success message after 2 seconds.
demo - http://alexmost.github.io/RxReact/hello_world2/public/index.html
code - https://github.com/AlexMost/RxReact/tree/master/hello_world2/hello_world2
dispatchActions = (view, eventStream, store) ->
incrementClickStream = ...
decrementClickStream = eventStream
.filter(({action}) -> action is "decrement_click_count")
.do(-> store.decrementClickscount())
.share()
countClicksStream = Rx.Observable
.merge(incrementClickStream, decrementClickStream)
showSavedMessageStream = countClicksStream
.throttle(1000)
.distinct(-> store.getClicksCount())
.flatMap(-> saveToDb store.getClicksCount())
.do(-> store.enableSavedMessage())
hideSavedMessageStream = showSavedMessageStream.delay(2000)
.do(-> store.disableSavedMessage())
// merge ...
dispatchActions = (view, eventStream, store) ->
incrementClickStream = ...
decrementClickStream = eventStream
.filter(({action}) -> action is "decrement_click_count")
.do(-> store.decrementClickscount())
.share()
countClicksStream = Rx.Observable
.merge(incrementClickStream, decrementClickStream)
showSavedMessageStream = countClicksStream
.throttle(1000)
.distinct(-> store.getClicksCount())
.flatMap(-> saveToDb store.getClicksCount())
.do(-> store.enableSavedMessage())
hideSavedMessageStream = showSavedMessageStream.delay(2000)
.do(-> store.disableSavedMessage())
// merge ...
decrement click stream
dispatchActions = (view, eventStream, store) ->
incrementClickStream = ...
decrementClickStream = eventStream
.filter(({action}) -> action is "decrement_click_count")
.do(-> store.decrementClickscount())
.share()
countClicksStream = Rx.Observable
.merge(incrementClickStream, decrementClickStream)
showSavedMessageStream = countClicksStream
.throttle(1000)
.distinct(-> store.getClicksCount())
.flatMap(-> saveToDb store.getClicksCount())
.do(-> store.enableSavedMessage())
hideSavedMessageStream = showSavedMessageStream.delay(2000)
.do(-> store.disableSavedMessage())
// merge ...
using merge to produce all clicks stream
dispatchActions = (view, eventStream, store) ->
incrementClickStream = ...
decrementClickStream = eventStream
.filter(({action}) -> action is "decrement_click_count")
.do(-> store.decrementClickscount())
.share()
countClicksStream = Rx.Observable
.merge(incrementClickStream, decrementClickStream)
showSavedMessageStream = countClicksStream
.throttle(1000)
.distinct(-> store.getClicksCount())
.flatMap(-> saveToDb store.getClicksCount())
.do(-> store.enableSavedMessage())
hideSavedMessageStream = showSavedMessageStream.delay(2000)
.do(-> store.disableSavedMessage())
// merge ...
per 1 second
if clicks counter value changed
sync with server, wait for responce
showsuccesss message
dispatchActions = (view, eventStream, store) ->
incrementClickStream = ...
decrementClickStream = eventStream
.filter(({action}) -> action is "decrement_click_count")
.do(-> store.decrementClickscount())
.share()
countClicksStream = Rx.Observable
.merge(incrementClickStream, decrementClickStream)
showSavedMessageStream = countClicksStream
.throttle(1000)
.distinct(-> store.getClicksCount())
.flatMap(-> saveToDb store.getClicksCount())
.do(-> store.enableSavedMessage())
hideSavedMessageStream = showSavedMessageStream.delay(2000)
.do(-> store.disableSavedMessage())
// merge ...
create hide message stream
from previous
Conclusion
● Rx is a powerful tool for writing async code
● Helps to write modular and composable code
● Solves a lot of hard testing problems
● Solves problem of error handling and resource
management
Resources
http://rxmarbles.com/
http://jaredforsyth.com/rxvision/
https://github.com/jhusain/learnrx
Talk demos here
https://github.com/AlexMost/kyivjs_2015
Thanks : D

Rxjs kyivjs 2015

  • 1.
  • 2.
    About me Mostovenko Alexander Pythondeveloper at Writing mostly in python and coffeescript. Love FP stuff. Traveling, snowboarding, cycling. https://twitter.com/MostovenkoA https://github.com/AlexMost
  • 3.
    Why Rx.js andnot another FRP lib? ● Implemented in a lot of languages (.NET, Java, Ruby, Python, Clojure, Scala, Haskell, Objective-C). ● Has a huge amount of docs and examples. ● Why not?
  • 4.
  • 5.
    Async code injavascript ● Some computation with delayed result. (data fetch, animations, DOM operations) ● Reacting to events (event handlers)
  • 6.
    Sync vs Asyncdataflow
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
    Asynchronous dataflow (autocomplete) time Actions keyup per2 seconds ajax request ajax response show suggest
  • 19.
    Asynchronous dataflow (autocomplete) time Actions keyup per2 seconds ajax request ajax response show suggest
  • 20.
    Asynchronous dataflow (autocomplete) time Actions keyup per2 seconds ajax request ajax response show suggest Ooooops
  • 21.
    Problems with asynccode 1. How to manage async flow in code? 2. How to handle errors? 3. How to compose?
  • 22.
  • 23.
    Callbacks ● Event notification- Yes ● Manage control flow - Nooooooooooo
  • 24.
    Callback hell async_func1(function(err, data){ async_func2(function(err,data){ async_func3(function(err, data){ async_func4(function(err, data){ }); }); }); });
  • 25.
    Callback hell yeah!!! async_func1(function(err, data){ if (!err) { async_func2(function(err, data){ if (!err) { async_func3(function(err, data){ if (!err) { async_func4(function(err, data){ }); } else { log(err); } }); } else { log(err); } }); } else { log(err); } });
  • 26.
    Callback hell yeah!!! async_func1(function(err, data){ if (!err) { async_func2(function(err, data){ if (!err) { async_func3(function(err, data){ if (!err) { async_func4(function(err, data){ }); } else { log(err); } }); } else { log(err); } }); } else { log(err); } }); UNREADABLE !!! UNMANAGABLE !!! UNTESTABLE !!! UNCOMPOSABLE !!! PIECE OF …. CODE
  • 28.
    Monad A monad isjust a monoid in the category of endofunctors
  • 29.
    Callback vs Blackhole some_async_func(function(err, result) { // ... looks like we are in trap another_wonderful_func(function(err, result){ // ... you are in trap }); });
  • 30.
    Callback vs BlackHole 1. No return value (undefined)
  • 31.
    Callback vs BlackHole 1. No return value (undefined) 2. One way ticket (callback hell)
  • 32.
  • 33.
    Promise var promise =new Promise((resolve, reject) => { // ... some async staff }); promise.then( (result) => {}, (error) => {} )
  • 34.
    Promise 1. Step forwardfrom callbacks for control flow managing 2. Error handling
  • 35.
    Why we shouldlook at Observable? 1. Promise represents only single value. 2. How to cancel? 3. Lazy execution. 4. We can work with Observable as easy as with promise.
  • 36.
    Promise is Observableonly with a single value
  • 37.
    Observable The Observable objectrepresents a push based collection.
  • 38.
    Observable var source =Rx.Observable.create((observer) => // some async operation observer.onNext(data) // or .. observer.onError(data) // or .. observer.onCompleted(data) ); source.subscribe( (data) => {} (err) => {} )
  • 39.
    Observlable vs promiseexample var source = Rx.Observable.create((observer) => { setTimeout(() => { console.log("observable timeout hit"); observer.onNext(25); }, 500); console.log('observable started')}); source.subscribe(x => console.log(`observable value ${x}`)); Observable > observable started > observable timeout hit > observable value 25
  • 40.
    Observlable vs promiseexample var promise = new Promise((resolve) => { setTimeout(() => { console.log("promise timeout hit"); resolve(25); }, 500); console.log("promise started");}); promise.then(x => console.log(`promise value ${x}`)); Promise > promise started > promise timeout hit > promise value 25
  • 41.
    Lazy execution var source= Rx.Observable.create((observer) => { setTimeout(() => { console.log("observable timeout hit"); observer.onNext(25); }, 500); console.log('observable started')}); // source.subscribe(x => console.log(`observable value ${x}`)); >
  • 42.
    Observable can becanceled var source = Rx.Observable.create((observer) => { var id = setTimeout(() => { console.log("observable timeout hit"); observer.onNext(25); }, 500); console.log('observable started'); return () => { console.log("dispose called") clearTimeout(id);} }); var disposable = source.subscribe( x => console.log(`observable value ${x}`)); setTimeout(() => disposable.dispose(), 100) > observable started > dispose called
  • 43.
    Asynchronous programming landscape multiple values single value syncasync a = f(x) a = f(x).then(...) collection = [1, 2, 3] a = collection .filter((v) => v > 2) Promise Array Sync value ?
  • 44.
    Asynchronous programming landscape multiple values single value syncasync a = f(x) a = f(x).then(...) collection = [1, 2, 3] a = collection .filter((v) => v > 2) move = Rx.Observable.fromEvent(document, 'mousemove') my_moves = move .filter((ev) => ev.clientX > 100) my_moves.subscribe(...) Promise Array value Observable
  • 45.
    How to createObservable ● Observable.create ● Observable.fromEvent ● Observable.fromNodeCallback ● Observable.fromArray ● Observable.fromPromise
  • 46.
    Drag & Dropexample What if i told you …. That mouse move event is an array and you can map, filter over it?
  • 47.
    var dragTarget =document.getElementById('dragTarget'); // Get the three major events collections var mouseup = Rx.DOM.fromEvent(dragTarget, 'mouseup'); var mousemove = Rx.DOM.fromEvent(document, 'mousemove'); var mousedown = Rx.DOM.fromEvent(dragTarget, 'mousedown'); var mousedrag = mousedown.flatMap(({offsetX, offsetY}) => { return mousemove .map(({clientX: x, clientY: y}) => { return {left: x - offsetX, top: y - offsetY}}) .takeUntil(mouseup) }); var subscription = mousedrag.subscribe((pos) => { dragTarget.style.top = pos.top + 'px'; dragTarget.style.left = pos.left + 'px'; });
  • 48.
    var dragTarget =document.getElementById('dragTarget'); // Get the three major events collections var mouseup = Rx.DOM.fromEvent(dragTarget, 'mouseup'); var mousemove = Rx.DOM.fromEvent(document, 'mousemove'); var mousedown = Rx.DOM.fromEvent(dragTarget, 'mousedown'); var mousedrag = mousedown.flatMap(({offsetX, offsetY}) => { return mousemove .map(({clientX: x, clientY: y}) => { return {left: x - offsetX, top: y - offsetY}}) .takeUntil(mouseup) }); var subscription = mousedrag.subscribe((pos) => { dragTarget.style.top = pos.top + 'px'; dragTarget.style.left = pos.left + 'px'; }); Create observable collections from DOM events
  • 49.
    var dragTarget =document.getElementById('dragTarget'); // Get the three major events collections var mouseup = Rx.DOM.fromEvent(dragTarget, 'mouseup'); var mousemove = Rx.DOM.fromEvent(document, 'mousemove'); var mousedown = Rx.DOM.fromEvent(dragTarget, 'mousedown'); var mousedrag = mousedown.flatMap(({offsetX, offsetY}) => { return mousemove .map(({clientX: x, clientY: y}) => { return {left: x - offsetX, top: y - offsetY}}) .takeUntil(mouseup) }); var subscription = mousedrag.subscribe((pos) => { dragTarget.style.top = pos.top + 'px'; dragTarget.style.left = pos.left + 'px'; }); Using power of Rx, combine existing event streams to produce mouse drag event collection
  • 50.
    var dragTarget =document.getElementById('dragTarget'); // Get the three major events collections var mouseup = Rx.DOM.fromEvent(dragTarget, 'mouseup'); var mousemove = Rx.DOM.fromEvent(document, 'mousemove'); var mousedown = Rx.DOM.fromEvent(dragTarget, 'mousedown'); var mousedrag = mousedown.flatMap(({offsetX, offsetY}) => { return mousemove .map(({clientX: x, clientY: y}) => { return {left: x - offsetX, top: y - offsetY}}) .takeUntil(mouseup) }); var subscription = mousedrag.subscribe((pos) => { dragTarget.style.top = pos.top + 'px'; dragTarget.style.left = pos.left + 'px'; }); Subscribe on mouse drag events, updating top and left attributes
  • 51.
    Observable are firstclass events Can be: - passed as a parameter - returned from a function - assigned to a variable
  • 53.
    Some common basicoperations map filter reduce zip merge flatMap ... https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md
  • 54.
  • 55.
  • 56.
    var mousedrag_in_area =mousedrag.filter(({top, left}) => { return top <= 200 && left <= 200 }); var subscription = mousedrag_in_area.subscribe((pos) => { dragTarget.style.top = pos.top + 'px'; dragTarget.style.left = pos.left + 'px'; }); let’s filter drag area
  • 57.
  • 58.
    let’s create slowmouse drag var slow_mousedrag = mousedrag.delay(700) var subscription = slow_mousedrag.subscribe((pos) => { dragTarget.style.top = pos.top + 'px'; dragTarget.style.left = pos.left + 'px'; });
  • 59.
  • 60.
    let’s record ourdrag path var drag_path = mousedrag .takeUntil(mouseup) .reduce(((a, b) => a + `${b.top} ${b.left};`), "") drag_path.subscribe((path) => pathTarget.innerHTML = path)
  • 61.
  • 62.
    flatMap var mousedrag =mousedown.flatMap(({offsetX, offsetY}) => { return mousemove .map(({clientX: x, clientY: y}) => { return {left: x - offsetX, top: y - offsetY}}) .takeUntil(mouseup) });
  • 63.
    flatMap var mousedrag =mousedown.flatMap(({offsetX, offsetY}) => { return mousemove .map(({clientX: x, clientY: y}) => { return {left: x - offsetX, top: y - offsetY}}) .takeUntil(mouseup) }); Outer stream
  • 64.
    flatMap var mousedrag =mousedown.flatMap(({offsetX, offsetY}) => { return mousemove .map(({clientX: x, clientY: y}) => { return {left: x - offsetX, top: y - offsetY}}) .takeUntil(mouseup) }); value as a stream
  • 65.
  • 66.
    merge var btnTarget =document.getElementById('okbtn'); var buttonClicks = Rx.DOM.fromEvent(btnTarget, 'click'); var move_to_100 = buttonClicks.map(() => {return {left: 100, top: 100}}); var change_position = Rx.Observable.merge(mousedrag, move_to_100) change_position.subscribe((pos) => { dragTarget.style.top = pos.top + 'px'; dragTarget.style.left = pos.left + 'px'; });
  • 67.
    merge var btnTarget =document.getElementById('okbtn'); var buttonClicks = Rx.DOM.fromEvent(btnTarget, 'click'); var move_to_100 = buttonClicks.map(() => {return {left: 100, top: 100}}); var change_position = Rx.Observable.merge(mousedrag, move_to_100) change_position.subscribe((pos) => { dragTarget.style.top = pos.top + 'px'; dragTarget.style.left = pos.left + 'px'; }); new stream
  • 68.
    And a lotof other methods …. ● amb ● and ● asObservable ● average ● buffer ● bufferWithCount ● bufferWithTime ● bufferWithTimeOrCount ● catch | catchError ● combineLatest ● concat ● concatAll ● concatMap ● concatMapObserver ● connect ● includes ● controlled ● count ● debounce ● debounceWithSelector ● defaultIfEmpty ● delay ● delaySubscription ● delayWithSelector ● dematerialize ● distinct ● distinctUntilChanged ● do ● doOnNext ● doOnError ● doOnCompleted ● doWhile ● elementAt ● elementAtOrDefault ● every ● expand ● extend ● filter ● find ● findIndex ● first ● firstOrDefault ● flatMap ● flatMapObserver ● flatMapLatest ● forkJoin ● groupBy ● groupByUntil ● groupJoin ● ignoreElements ● indexOf ● isEmpty ● join ● jortSort ● jortSortUntil ● last ● lastOrDefault ● merge ● mergeAll ● min ● minBy ● multicast ● observeOn ● onErrorResumeNext ● pairwise ● partition ● pausable ● pausableBuffered ● pluck ● publish ● publishLast ● publishValue ● share ● shareReplay ● shareValue ● refCount ● reduce ● repeat ● replay ● retry ● retryWhen ● sample ● scan ● select ● selectConcat ● selectConcatObserver ● selectMany ● selectManyObserver ● selectSwitch ● sequenceEqual ● single ● singleOrDefault ● singleInstance ● skip ● skipLast ● skipLastWithTime ● skipUntil https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md
  • 69.
    Testing with timemachine Rx.TestScheduler
  • 70.
    var mult_stream =some_stream.debounce(100).map((n) => n * 2) assertRxActions( mult_stream, [ [100, {value: 20}], [200, {value: 40}] ], [ [200, {value: 80}] ], (result) => assertTrue(result) )
  • 71.
    var mult_stream =some_stream.debounce(100).map((n) => n * 2) assertRxActions( mult_stream, [ [100, {value: 20}], [200, {value: 40}] ], [ [200, {value: 80}] ], (result) => assertTrue(result) ) stream to test
  • 72.
    var mult_stream =some_stream.debounce(100).map((n) => n * 2) assertRxActions( mult_stream, [ [100, {value: 20}], [200, {value: 40}] ], [ [200, {value: 80}] ], (result) => assertTrue(result) ) Input stream messages
  • 73.
    var mult_stream =some_stream.debounce(100).map((n) => n * 2) assertRxActions( mult_stream, [ [100, {value: 20}], [200, {value: 40}] ], [ [200, {value: 80}] ], (result) => assertTrue(result) ) time
  • 74.
    var mult_stream =some_stream.debounce(100).map((n) => n * 2) assertRxActions( mult_stream, [ [100, {value: 20}], [200, {value: 40}] ], [ [200, {value: 80}] ], (result) => assertTrue(result) ) Expected result at time
  • 75.
    var mult_stream =some_stream.debounce(100).map((n) => n * 2) assertRxActions( mult_stream, [ [100, {value: 20}], [200, {value: 40}] ], [ [200, {value: 80}] ], (result) => assertTrue(result) ) callback for assert
  • 76.
    React ? ● Rx-React ●RxReact ● cycle-react ● React RxJS Autocomplete ● React RxJS TODO MVC ● Rx TODO MVC ● React RxJS Router ● React + RxJS + Angular 2.0 di.js TODO MVC ● React + RxJS Reactive Cube ● Real-Time with React + RxJS + Meteor ● React + RxJS Flow ● Reactive Widgets ● React RxJS Infinite Scroll
  • 77.
    Flux ? ● Rx-Flux ●ReactiveFlux ● Thundercats.js ● Flurx ● RR
  • 78.
    RxReact some info -http://habrahabr.ru/post/251835/ repo with demos - https://github.com/AlexMost/RxReact
  • 79.
    RxReact (simple clickcount) Simple case demo - http://alexmost.github.io/RxReact/hello_world/public/index.html code - https://github.com/AlexMost/RxReact/tree/master/hello_world/hello_world
  • 80.
    dispatchActions = (view,eventStream, store) -> incrementClickStream = eventStream .filter(({action}) -> action is "increment_click_count") .do(-> store.incrementClicksCount()) Rx.Observable.merge( incrementClickStream # some more actions here for updating view ... ).subscribe( -> view.setProps getViewState(store) (err) -> console.error? err) RxReact (simple click count)
  • 81.
    dispatchActions = (view,eventStream, store) -> incrementClickStream = eventStream .filter(({action}) -> action is "increment_click_count") .do(-> store.incrementClicksCount()) Rx.Observable.merge( incrementClickStream # some more actions here for updating view ... ).subscribe( -> view.setProps getViewState(store) (err) -> console.error? err) RxReact (simple click count) stream from view for increment and storage modification
  • 82.
    dispatchActions = (view,eventStream, store) -> incrementClickStream = eventStream .filter(({action}) -> action is "increment_click_count") .do(-> store.incrementClicksCount()) Rx.Observable.merge( incrementClickStream # some more actions here for updating view ... ).subscribe( -> view.setProps getViewState(store) (err) -> console.error? err) RxReact (simple click count) merge all streams that can modify UI
  • 83.
    dispatchActions = (view,eventStream, store) -> incrementClickStream = eventStream .filter(({action}) -> action is "increment_click_count") .do(-> store.incrementClicksCount()) Rx.Observable.merge( incrementClickStream # some more actions here for updating view ... ).subscribe( -> view.setProps getViewState(store) (err) -> console.error? err) RxReact (simple click count) update UI, making setProps on view component
  • 84.
    RxReact (simple clickcount) More complex case 1. Decrement. 2. Sync with server (per 1 second). 3. Sync only if value changed. 4. Show sync success message. 5. Hide success message after 2 seconds. demo - http://alexmost.github.io/RxReact/hello_world2/public/index.html code - https://github.com/AlexMost/RxReact/tree/master/hello_world2/hello_world2
  • 85.
    dispatchActions = (view,eventStream, store) -> incrementClickStream = ... decrementClickStream = eventStream .filter(({action}) -> action is "decrement_click_count") .do(-> store.decrementClickscount()) .share() countClicksStream = Rx.Observable .merge(incrementClickStream, decrementClickStream) showSavedMessageStream = countClicksStream .throttle(1000) .distinct(-> store.getClicksCount()) .flatMap(-> saveToDb store.getClicksCount()) .do(-> store.enableSavedMessage()) hideSavedMessageStream = showSavedMessageStream.delay(2000) .do(-> store.disableSavedMessage()) // merge ...
  • 86.
    dispatchActions = (view,eventStream, store) -> incrementClickStream = ... decrementClickStream = eventStream .filter(({action}) -> action is "decrement_click_count") .do(-> store.decrementClickscount()) .share() countClicksStream = Rx.Observable .merge(incrementClickStream, decrementClickStream) showSavedMessageStream = countClicksStream .throttle(1000) .distinct(-> store.getClicksCount()) .flatMap(-> saveToDb store.getClicksCount()) .do(-> store.enableSavedMessage()) hideSavedMessageStream = showSavedMessageStream.delay(2000) .do(-> store.disableSavedMessage()) // merge ... decrement click stream
  • 87.
    dispatchActions = (view,eventStream, store) -> incrementClickStream = ... decrementClickStream = eventStream .filter(({action}) -> action is "decrement_click_count") .do(-> store.decrementClickscount()) .share() countClicksStream = Rx.Observable .merge(incrementClickStream, decrementClickStream) showSavedMessageStream = countClicksStream .throttle(1000) .distinct(-> store.getClicksCount()) .flatMap(-> saveToDb store.getClicksCount()) .do(-> store.enableSavedMessage()) hideSavedMessageStream = showSavedMessageStream.delay(2000) .do(-> store.disableSavedMessage()) // merge ... using merge to produce all clicks stream
  • 88.
    dispatchActions = (view,eventStream, store) -> incrementClickStream = ... decrementClickStream = eventStream .filter(({action}) -> action is "decrement_click_count") .do(-> store.decrementClickscount()) .share() countClicksStream = Rx.Observable .merge(incrementClickStream, decrementClickStream) showSavedMessageStream = countClicksStream .throttle(1000) .distinct(-> store.getClicksCount()) .flatMap(-> saveToDb store.getClicksCount()) .do(-> store.enableSavedMessage()) hideSavedMessageStream = showSavedMessageStream.delay(2000) .do(-> store.disableSavedMessage()) // merge ... per 1 second if clicks counter value changed sync with server, wait for responce showsuccesss message
  • 89.
    dispatchActions = (view,eventStream, store) -> incrementClickStream = ... decrementClickStream = eventStream .filter(({action}) -> action is "decrement_click_count") .do(-> store.decrementClickscount()) .share() countClicksStream = Rx.Observable .merge(incrementClickStream, decrementClickStream) showSavedMessageStream = countClicksStream .throttle(1000) .distinct(-> store.getClicksCount()) .flatMap(-> saveToDb store.getClicksCount()) .do(-> store.enableSavedMessage()) hideSavedMessageStream = showSavedMessageStream.delay(2000) .do(-> store.disableSavedMessage()) // merge ... create hide message stream from previous
  • 90.
    Conclusion ● Rx isa powerful tool for writing async code ● Helps to write modular and composable code ● Solves a lot of hard testing problems ● Solves problem of error handling and resource management
  • 91.
  • 92.
  • 93.