FRP with RxJS
Aliaksei Bahachuk
Reactive Extensions
Reactive Extensions is a library to compose async and event-based programs using observable collections and query operators.
Rx is everywhere
Java
JavaScript
.Net
Ruby
Swift
C++
Clojure
Scala
What is the most asynchronous?
User
More async data streams
-
XHR requests
-
WebSocket
-
Promises
-
Fibers
-
Events
-
Dom Inputs
-
Web Worker
-
Web RTC
Stream creators
-
Rx.jquery
-
Rx.Observable.create
-
Rx.Observable.fromEvent
-
Rx.Observable.fromCallback
-
Rx.Observable.fromNodeCallback
-
Rx.Observable.fromPromise
Stream operators
RxJS operators
RxJS operators: map
resStream
RxJS operators: delay
resStream
RxJS operators: debounce
resStream
RxJS operators: merge
RxJS operators: combineLatest
RxJS operators: zip
Let's create a morse-code decoder!
Morse-code decoder
click...
click...
click.............
click...
Morse-code decoder
click...
click...
click.............
click...
Morse-code decoder
-
Dom events (keydown, keyup)
-
Start signal/End signal
-
Dot/Dash/Whitespace
-
Letter
-
Word/Sentence
Morse-code decoder
Morse-code decoder
-
"." = ~400ms
-
"_" = 3 * "."
-
between codes = "."
-
between letters = 3 * "."
-
between words = 7 * "."
Morse-code decoder
const keyUps = Rx.Observable
.fromEvent(document, 'keyup');
const keyDowns = Rx.Observable
.fromEvent(document, 'keydown');
Morse-code decoder
const spaceKeyUps = keyUps.filter((data) => data.keyCode === 32);
const spaceKeyDowns = keyDowns.filter((data) => data.keyCode === 32);
Morse-code decoder
const signalStartsRaw = spaceKeyDowns.map(() => "start");
const signalEndsRaw = spaceKeyUps.map(() => "end");
const signalStartsEnds = Rx.Observable
.merge(signalStartsRaw, signalEndsRaw)
.distinctUntilChanged();
const signalStarts = signalStartsEnds
.filter((ev) => ev === "start");
const signalEnds = signalStartsEnds
.filter((ev) => ev === "end");
Morse-code decoder
const signalStarts = signalStartsEnds
.filter((ev) => ev === "start")
.timestamp();
const signalEnds = signalStartsEnds
.filter((ev) => ev === "end")
.timestamp();
const spanStream = signalStarts.flatMap((start) => {
return signalEnds.map((end) => end.timestamp - start.timestamp)
.first();
});
Morse-code decoder
Morse-code decoder
const SPAN = 400;
const dotsStream = spanStream
.filter((v) => v <= SPAN)
.map(() => ".");
const lineStream = spanStream
.filter((v) => v > SPAN)
.map(() => "-");
Morse-code decoder
const dotsAndLines = Rx.Observable.merge(dotsStream, lineStream);
// [['.', '.', '-'], ['-', '.', '-'] ... ]
const letterCodes = dotsAndLines.buffer(letterWhitespaces);
// ['A', 'B' ...]
const lettersStream = letterCodes
.map((codes) => morse.decode(codes.join("")));
Morse-code decoder
demo — http://alexmost.github.io/morse/
source — https://github.com/AlexMost/morse
Morse-code decoder
What else?
Component bindings
Component bindings
click...
click...
click.............
click...
click
category.
comp.
bundle.
package.
Component bindings
Component bindings
RxJS with other frameworks
RxJS with Angular - rx.angular.js
angular.module('example', ['rx'])
.controller('AppCtrl', function($scope, $http, rx) {
function searchWikipedia (term) {
return rx.Observable
.fromPromise($http({}))
.map(function(response){ return response.data[1]; });
}
$scope.search = '';
$scope.results = [];
$scope.$createObservableFunction('click')
.map(function () { return $scope.search; })
.flatMapLatest(searchWikipedia)
.subscribe(function(results) {
$scope.results = results;
});
});
RxJS with Angular2 - inside
RxJS with React - rx-react
RxJS with others
Alternative?
Bacon
Kefir
Highland
Tests
stream1.combine(stream2, ->) (1000 samples)
----------------------------------------------------------------
Kefir w/o subscr. 0.44 KiB w/ subscr. +0.80 KiB sum 1.23 KiB
Bacon w/o subscr. 5.42 KiB w/ subscr. +6.15 KiB sum 11.57 KiB
Rx w/o subscr. 0.43 KiB w/ subscr. +2.84 KiB sum 3.26 KiB
-----------------------
Kefir 1.00 1.00 1.00 Bacon 12.45 7.71 9.39 Rx 0.98 3.56 2.65
.filter(->) (1000 samples)
----------------------------------------------------------------
Kefir w/o subscr. 0.31 KiB w/ subscr. +0.46 KiB sum 0.77 KiB
Bacon w/o subscr. 1.82 KiB w/ subscr. +2.49 KiB sum 4.31 KiB
Rx w/o subscr. 0.37 KiB w/ subscr. +1.44 KiB sum 1.81 KiB
-----------------------
Kefir 1.00 1.00 1.00 Bacon 5.91 5.39 5.60 Rx 1.21 3.11 2.35
.map(->) (1000 samples)
----------------------------------------------------------------
Kefir w/o subscr. 0.30 KiB w/ subscr. +0.47 KiB sum 0.77 KiB
Bacon w/o subscr. 1.81 KiB w/ subscr. +2.49 KiB sum 4.30 KiB
Rx w/o subscr. 0.37 KiB w/ subscr. +1.43 KiB sum 1.81 KiB
-----------------------
Kefir 1.00 1.00 1.00 Bacon 6.07 5.31 5.60 Rx 1.24 3.06 2.35
I want more hardcore!
Javelin and ClosureScript
(defc test-results
{:scores [74 51 97 88 89 91 72 77 69 72 45 63]
:proctor "Mr. Smith"
:subject "Organic Chemistry"
:sequence "CHM2049"})
(defc= test-results-with-mean
(let [scores (:scores test-results)
mean (/ (reduce + scores) (count scores))
grade (cond (<= 90 mean) :A
(<= 80 mean) :B
(<= 70 mean) :C
(<= 60 mean) :D
:else :F)]
(assoc test-results :mean mean :grade grade)))
Cycle.js
-
RxJS
-
Side effects/Logic
-
Composition
-
Streams, streams, streams, streams
-
hJSX/JSX
Questions?
RxJs, Bacon, Kefir
By Egor Miasnikov
RxJs, Bacon, Kefir
An overview of RxJs, Bacon, Kefir
- 804