RX: Reactive Extensions in C#, Java und Javascript
Martin Baum
Was'n das ?
"composing asynchronous and event-based programs using observable sequences and fluent operators"
Rx ist eine DSL für Eventhandling
Warum Rx?
Eventhandling
button.onclick = function(event) {
...
};
Callback Hell!
var ws = new WebSocketManagement();
ws.loadDataPool.call(that, function(){
ws.loadDevices.call(that, function(){
ws.loadBriefcase.call(that, function(){
ws.loadSettings.call(that, function(){
//console.log("finished loading settings");
ws.loadVisualElements.call(that, function(){
...
});
});
});
});
});
Error Handling
object.asyncMethod(function(err, data) {
if (err) throw err; // gone nowhere
});
// how to cancel that ?
$.post('/answer/never', function(data) {
});
Andere Ansätze
- Promises / Futures
- Async/Await
- Observer / Publish-Subcribe
Was ist der Unterschied zwischen einem Array und einem Event ?
// my mouseclicks
[{x: 45, y: 0}, {x: 48, y: 2}, {x: 52, y: 8}, ...]
// my filtered mouseclicks
[{x: 45, y: 0}, {x: 48, y: 2}, {x: 52, y: 8}, ...]
.map(data => {return x + y})
.filter(data => {return data > 10});
// my filtered mouseclicks
var stream = Rx.Observable.fromEvent(button, "click")
.map(data => {return x + y})
.filter(data => {return data > 10});
// my filtered mouseclicks
var stream = Rx.Observable.fromEvent(button, "click")
.map(data => {return x + y})
.filter(data => {return data > 10});
stream.subscribe(function(data) {
// wird bei jedem Click ausgeführt
})
Eventstreams erzeugen
- Async Operationen
- Manuell
- Datenstrukturen, Interval, zeitgesteuert
Eventstreams erzeugen aus Promises
kompatibel zu A/+ bzw. ES2o15
var promise = new Promise((resolve, reject) => {
});
Rx.Observable.fromPromise(promise)
auch mit Funktion möglich
function promAjax(to) {
return $.ajax({
url: to,
.....
}).promise();
}
Rx.Observable.fromPromise(promAjax)
Callbacks
myApi.openSession(username, password, function(session) { ...});
let open = Rx.Observable.fromCallback(myApi.openSession);
}).first();
open(username, password);
Eventstreams erzeugen
Rx.Observable.return(32);
Rx.Observable.just(32);
Rx.Observable.throws('Im an error');
Rx.Observable.interval(100);
Rx.Observable.timer(100, 500).timestamp();
Rx.Observable.from([1, 2, 3, 4, 5]);
Rx.Observable.from(new Map([[key1, 10] [key2, 20]]));
Eventstreams erzeugen
wird Teil von ES7:
Rx.Observable.create(observer => {
doSomethingAsync(
function(data) { onNext(data)}),
function(error) { onError(error)}
});
....
// some time later
onCompleted(data);
});
Promises ?
Promise: Eine Antwort
Observable Stream: Stream von Antworten
Query Operatoren
- Sammlung von Keywords - Vergleichbar mit String- oder Arrayfunktionen
- ermöglichen kreatives Arbeiten mit Streams
- Können hintereinander geschaltet werden
- CheatSheet: RxMarbles
Streams filtern
- Selektion, Projektion: filter, map, distinct
- zeitlicher Abstand: throttle, sample, delay, debounce
- einzelne Elemente herauspicken: first, last, skip, skipUntil
- Events zu Listen zusammenfassen: buffer
- Streams vereinigen: concat, merge, zip
- Streams aufsplitten groupBy
Eigener Observer
debugObs = Rx.Observer.create(
d => console.log(d.data),
e => console.log(e),
f => console.log('eventsream finished!')
);
// eigenen Observer anwenden
stream.subscribe(debugObs);
Defer
Factory für Sreams einschieben
let stream = Rx.Observable.defer(() => {
// do something for buildup here
...
return Rx.Observable.return(42);
})
stream.subscribe() // jetzt wird defer ausgeführt
ForkJoin
Rx.Observable.forkJoin(
queryUrl('https://service.com'),
queryUrl('http://test.org'),
(data, data2) => {
// do something with the data here
}
)
Rx ErrorHandling
- Observable.subscribe() hat einen zweiten Parameter für ErrorHandler
- Timeout für Streams
- Retry Sequenz
- Fehler ignorieren: onErrorResumeNext
- Aufräumen mit finally
Rx ErrorHandling
stream.subscribe(
data => console.log(data),
error => throw error // that is catchable now!
);
Catch / Finally
Errorhandling kann durch Opertoren in der Sequenz erfolgen
Rx.Observable.fromEvent('test').catch(e => {
if (e instanceof MyError) return Rx.Observable.empty();
}).finally(() => { connection.close()});
onErrorResumeNext
Durchprobieren mehrer Streams
Rx.Observable.onErrorResumeNext(
queryUrl(mainUrl).retry(3),
queryUrl(fallbackUrl).timeout(500)
).finally(() => displayErrorMessage())
Cold Observable
Events kommen erst, wenn subscribed wird
var stream = Rx.Observable.interval(500);
stream.subscribe(function(data){
// fängt mit 1 an
});
Hot Observable
- Eventstream emitted sobald bzw. solange es Zuhörer gibt
- Entkopplung von Start und subsiption
- Single Source für Daten pro Observer
Hot Observable
legt mit connect() los.
// hier werden bereits Events entgegengenommen
let stream = Rx.Observable.interval(500);
// Konvertierung zum Hot Observable
let hot = stream.publish();
hot.subscribe(observer) // noch nichts empfangen
// verbinde Observable mit Daten - jetzt geht es los
hot.connect();
RefCount
// hier werden bereits Events entgegengenommen
let stream = Rx.Observable.interval(500);
// mach mich heiss
let hot = stream.publish().refCount();
Weitere Tools
- Subjects: Observer und Observable in Einem
- Schedulers: Zeitverhalten steuern
Use Cases
- GUIs
- Große Mengen an Events
- Diverse Eventquellen müssen miteinander arbeiten
- Overkill bei einzelnen Anfragen
- Angular 2 wird Rx statt Promises einsetzen
- Multiplattform Support
?????
baum@silpion.de
reactivex.io
RxJs Referenzen