rxjs evolved
TRANSCRIPT
RXJS EVOLVED
PAUL TAYLOR@trxcllnt
UI PLATFORM TEAM @
OBSERVABLE
EVENTEMITTER..... OBSERVABLE ≠ EVENTDISPATCHER
EVENTDELEGATE...
“The idea of future values — maybe.”
–ME
SINGLE MULTIPLE
SYNCHRONOUS Function Enumerable
ASYNCHRONOUS Promise Observable
THE OTHER “IDEAS” OF VALUES
SINGLE MULTIPLE
PULL Function Enumerable
PUSH Promise Observable
THE OTHER “IDEAS” OF VALUES
FUNCTIONS// The “idea” of a random number
var getRandomNumber = function() {
return Math.random();
}
// A random number
var rand = getRandomNumber.call();
(LAZINESS)
ANATOMY OF OBSERVABLECREATION
SUBSCRIPTION DISPOSAL
var randomNumbers = Observable.create((s) => {
var i = setTimeout(() => {
s.next(Math.random());
s.complete();
}, 1000);
return () => clearTimeout(i);
});
var sub = randomNumbers.subscribe({
next(x) { console.log(x); },
error(e) { console.error(e); },
complete() { console.log(“done”); }
});
var randomNumbers = Observable.create((s) => {
var i = setTimeout(() => {
s.next(Math.random());
s.complete();
}, 1000);
return () => clearTimeout(i);
});
var randomNumbers = Observable.create((s) => {
var i = setTimeout(() => {
s.next(Math.random());
s.complete();
}, 1000);
ANATOMY OF OBSERVABLE★ CREATION
★ SUBSCRIPTION
★ DISPOSAL
var randomNumbers = Observable.create(
WHAT HAPPENS WHEN…var randomNumbers = Observable.create((s) => {
var i = setTimeout(() => {
s.next(Math.random());
s.complete();
}, 1000);
return () => clearTimeout(i);
});
randomNumbers.subscribe(x => console.log(‘1: ’ + x));
randomNumbers.subscribe(x => console.log(‘2: ’ + x));
>
> 1: 0.1231982301923192831231
> 2: 0.8178491823912837129834
>
THIS HAPPENS
EVENTEMITTER..... OBSERVABLE ≠ EVENTDISPATCHER
EVENTDELEGATE...
OBSERVABLE = FUNCTION...........
“Observable is a function that, when invoked, returns 0-∞ values between now and the end of time.”
–ME
OPERATORS
OPERATORSMETHODS THAT PERFORM
CALCULATIONS ON THE VALUES
MAP, FILTER, SCAN, REDUCE, FLATMAP, ZIP, COMBINELATEST, TAKE, SKIP, TIMEINTERVAL, DELAY, DEBOUNCE, SAMPLE, THROTTLE, ETC.
“lodash for events”–NOT ME
WRITING AN OPERATOR (OLD)class Observable {
constructor(subscribe) { this.subscribe = subscribe; }
map(selector) {
var source = this;
return new Observable((destination) => {
return source.subscribe({
next(x) { destination.next(selector(x)) },
error(e) { destination.error(e); },
complete() { destination.complete(); }
});
});
}
}
USING OPERATORSvar grades = { “a”: 100, “b+”: 89, “c-“: 70 };
Observable.of(“a”, “b+”, “c-”)
.map((grade) => grades[grade])
.filter((score) => score > grades[“b+”])
.count()
.subscribe((scoreCount) => {
console.log(scoreCount + “ students received A’s”);
});
SCHEDULERS
SCHEDULERSCENTRALIZED DISPATCHERS TO
CONTROL CONCURRENCY
IMMEDIATE TIMEOUT
REQUESTANIMATIONFRAME
USING SCHEDULERSObservable.range = function(start, length, scheduler) {
return new Observable((subscriber) => {
return scheduler.schedule(({ index, count }) => {
if (subscriber.isUnsubscribed) { return; }
else if (index >= end) {
subscriber.complete();
} else {
subscriber.next(count);
this.schedule({ index: index + 1, count: count + 1 });
}
}, { index: 0, count: start });
});
}
USING SCHEDULERSObservable.fromEvent(“mouseMove”, document.body)
.throttle(1, Scheduler.requestAnimationFrame)
.map(({ pageX, pageY }) => (<div
className=“red-circle”
style={{ top: pageX, left: pageY }} />
))
.subscribe((mouseDiv) => {
React.render(mouseDiv, “#app-container”);
});
RXJS NEXT (v5.0.0-alpha)github.com/ReactiveX/RxJS
CONTRIBUTORS
BEN LESH ANDRÈ STALTZ OJ KWON
github.com/ReactiveX/RxJS/graphs/contributors
PRIMARY GOALS★ MODULARITY
★ PERFORMANCE
★ DEBUGGING
★ EXTENSIBILITY
★ SIMPLER UNIT TESTS
import Observable from ‘@reactivex/rxjs/Observable’;
import ArrayObservable from
‘@reactivex/rxjs/observables/ArrayObservable’;
import reduce from ‘@reactivex/rxjs/operators/reduce’;
Observable.of = ArrayObservable.of;
Observable.prototype.reduce = reduce;
Observable.of(1, 2, 3, 4, 5)
.reduce((acc, i) => acc * i, 1)
.subscribe((result) => console.log(result));
MODULARITY
SPEED
4.3X* FASTER*AVERAGE
(UP TO 11X)
50-90% FEWER
ALLOCATIONS
MEMORY
10-90% SHORTER
CALL STACKS
DEBUGGING
DEBUGGINGFLATMAP EXAMPLE RXJS 4
DEBUGGINGFLATMAP EXAMPLE RXJS 5
DEBUGGINGSWITCHMAP EXAMPLE RXJS 4
DEBUGGINGSWITCHMAP EXAMPLE RXJS 5
PERFORMANCE + DEBUGGING
★ SCHEDULERS OVERHAUL
★ CLASS-BASED OPERATORS (“LIFT”)
★ UNIFIED OBSERVER + SUBSCRIPTION
★ FLATTENED DISPOSABLE TREE
★ REMOVE TRY-CATCH FROM INTERNALS
FLATMAP VS. LIFT
flatMap<T, R>(selector: (value: T) => Observable<R>): Observable<R>;
lift<T, R>(operator: (subscriber: Observer<T>) => Observer<R>): Observable<R>;
FLATMAP
Observable.prototype.map = function map(project) {
var source = this;
return new Observable(function (observer) {
return source.subscribe(
(x) => observer.next(project(x)),
(e) => observer.error(e),
( ) => observer.complete()
);
});
}
★ CLOSURE SCOPE
★ INFLEXIBLE TYPE
★ CLOSURES SHOULD BE ON A PROTOTYPE
Observable.prototype.map = function(project) => { return this.lift(new MapOperator(project));}
class MapOperator implements Operator { constructor(project) { this.project = project; } call(observer) { return new MapSubscriber(observer, this.project); }}
class MapSubscriber extends Subscriber { constructor(destination, project) { super(destination); this.project = project; } next(x) { this.destination.next(this.project(x)); }}
★ NO CLOSURES
★ DELEGATES NEW OBSERVABLE TO LIFT
★ USES SUBSCRIBER PROTOTYPE
LIFT
EXTENSIBILITY
★ ALLOW SUBCLASSING OBSERVABLE
★ MAINTAIN SUBJECT BI-DIRECTIONALITY
★ FUTURE BACK-PRESSURE SUPPORT?
SUBCLASS OBSERVABLEclass MouseObservable extends Observable {
constructor(source, operator) {
this.source = source;
this.operator = operator || ((x) => x);
}
lift(operator) { return new MouseObservable(this, operator); }
trackVelocity() {
return this.lift((destination) => {
return new VelocityScanningSubscriber(destination);
});
}
concatFrictionEvents(coefficientOfKineticFriction) { ... }
}
SUBCLASS OBSERVABLEObservable.fromEvent(“mousedown”, document.body)
.flatMap((downEvent) => {
return Observable.fromEvent(“mousemove”, window)
.let(_ => new MouseObservable(this))
.trackVelocity()
.takeUntil(Observable.fromEvent(“mouseup”, window))
.concatFrictionEvents(0.5)
})
.throttle(1, Schedulers.requestAnimationFrame)
.subscribe(({ x, y }) => {
item.style.top = y;
item.style.left = x;
});
MAINTAIN TWO-WAY SUBJECTSvar naviSocket = new SocketSubject(“ws://127.0.0.1/navi”)
.map(x => JSON.parse(x.data))
.throttle(100);
naviSocket.subscribe((msg) => {
if (msg == “hey, listen!”) {
naviSocket.next(“go away navi!”);
}
});
SIMPLER UNIT TESTSMARBLE DIAGRAMS AS UNIT TESTS
SIMPLER UNIT TESTSMARBLE DIAGRAMS AS UNIT TESTS
it('should filter out even values', function() {
var source = hot('--0--1--2--3--4--|');
var expected = '-----1-----3-----|';
expectObservable(source.filter(x => x % 2 == 1)).toBe(expected);
});
RXJS NEXT (v5.0.0-alpha)github.com/ReactiveX/RxJS