RxJS - Destroy the state machine!
Stenver Jerkku
Who am I?
Stenver Jerkku
CTO in eAgronom
Previous work in SaleMove,
Tartu University, Nortal and CGI
Functional and Reactive programming advocate
Hardcore D&D fan
BIG
async problems
By thinking differently about
Events
This is the story how we solved
and removed
state machines
I have worked with heavily async apps
- App startup
- Real-time Communication between people
- Real-time peer-to-peer DOM manipulation
- Data access
- Animations
- View/model binding
Async is hard
Callback hell
var engage = (visitorId, hangUpButton, callback) => {
var engageError, engagementMedia, preloadedDOM,
tryStartEngagement = () => {
if (engageError) {
cancelEngagement(engageError);
}else if (engagementMedia && preloadedDOM) {
callback(preloadedDOM, engagementMedia);
}
});
var prepareMedia = () => {
mediaDetector.detect((media, error) => {
engageError = error;
engagementMedia = media;
tryStartEngagement();
});
};
var preloadDOM = () => {
DOMpreloader.preload((dom, error) => {
engageError = error;
preloadedDOM = dom;
tryStartEngagement();
});
};
hangUpButton.addEventListener("click", () => { engageError = "Hanged up"; })
preloadDOM();
prepareMedia();
}
State
var engage = (visitorId, hangUpButton, callback) => {
var engageError, engagementMedia, preloadedDOM,
tryStartEngagement = () => {
if (engageError) {
cancelEngagement(engageError);
}else if (engagementMedia && preloadedDOM) {
callback(preloadedDOM, engagementMedia);
}
});
var prepareMedia = () => {
mediaDetector.detect((media, error) => {
engageError = error;
engagementMedia = media;
tryStartEngagement();
});
};
var preloadDOM = () => {
DOMpreloader.preload((dom, error) => {
engageError = error;
preloadedDOM = dom;
tryStartEngagement();
});
};
hangUpButton.addEventListener("click", () => { engageError = "Hanged up"; })
preloadDOM();
prepareMedia();
}
Error handling - no try-catch
var engage = (visitorId, hangUpButton, callback) => {
var engageError, engagementMedia, preloadedDOM,
tryStartEngagement = () => {
if (engageError) {
cancelEngagement(engageError);
}else if (engagementMedia && preloadedDOM) {
callback(preloadedDOM, engagementMedia);
}
});
var prepareMedia = () => {
mediaDetector.detect((media, error) => {
engageError = error;
engagementMedia = media;
tryStartEngagement();
});
};
var preloadDOM = () => {
DOMpreloader.preload((dom, error) => {
engageError = error;
preloadedDOM = dom;
tryStartEngagement();
});
};
hangUpButton.addEventListener("click", () => { engageError = "Hanged up"; })
preloadDOM();
prepareMedia();
}
memory leaks, race conditions
var engage = (visitorId, hangUpButton, callback) => {
var engageError, engagementMedia, preloadedDOM,
tryStartEngagement = () => {
if (engageError) {
cancelEngagement(engageError);
}else if (engagementMedia && preloadedDOM) {
callback(preloadedDOM, engagementMedia);
}
});
var prepareMedia = () => {
mediaDetector.detect((media, error) => {
engageError = error;
engagementMedia = media;
tryStartEngagement();
});
};
var preloadDOM = () => {
DOMpreloader.preload((dom, error) => {
engageError = error;
preloadedDOM = dom;
tryStartEngagement();
});
};
hangUpButton.addEventListener("click", () => { engageError = "Hanged up"; })
preloadDOM();
prepareMedia();
}
What is Reactive programming?
Let's find out together!
What is Reactive programming?
The reactive manifesto
From http://www.reactivemanifesto.org/
Reactive system are:
Responsive, Resilient, Elastic, Message Driven
The Microsoft definition
From Microsoft https://rx.codeplex.com/
The Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs using observable sequences and LINQ-style query operators.
Using Rx, developers represent asynchronous data streams with Observables, query asynchronous data streams using LINQ operators, and parameterize the concurrency in the asynchronous data streams using Schedulers.
Simply put, Rx = Observables + LINQ + Schedulers.
The Stack Overflow
Top rated comment to the accepted answer in SO:
http://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming
@Conal: you clearly know what you're talking about, but your language presumes I have a doctorate in computational mathematics, which I do not. I do have a background in systems engineering and 20+ years of experience with computers and programming languages, still I feel your response leaves me baffled.
I challenge you to repost your reply in english ;-)
From the API
Rx.Observable.prototype.flatMapLatest(selector, [thisArg])
An observable sequence which transforms the items emitted by an Observable into Observables, and mirror those items emitted by the most-recently transformed Observable.
From the API:
Reactive programming
is programming with asynchronous streams
Async streams are:
Mouse clicks
Events
??? what else can be a stream ?
Reactive programming
What is the difference between an array
and Event?
[{x: 10, y: 25}, {x: 12, y: 28}, {x: 13, y: 29}]
[{x: 1, y: 22}, {x: 2, y: 22}, ..., {x: 3, y: 22}]
Fundamentally, they both are
Collections
Array and Events are
Collections
Everything can be composed of arrays
Events are fundamentally collections and a stream
We can treat everything as a stream
Arrays are fundamentally collections and a stream
Reactive programming
Variables
User input
Properties
Caches
Data structures
etc.
Facebook infinite scroll is a stream just like mouse click events are
Streams are Cheap
This is where the functional magic kicks in
You can use a stream as in input to another stream. You can even use multiple streams as an input to one stream by
Or some other way
Streams are functional
They can be Mapped
They can be Filtered
They can be Debounced
Streams can be completed
This is just the tip of the iceberg
A library that helps you do Reactive programming
RxJS - Reactive extensions for javascript
Streams are observables
because they are observed
responseStream.subscribe(function(response) {
// render `response` to the DOM however you wish
});
In Rx, you create observables, i.e. streams
You subscribe to observables to listen for events
The functions listening on events are observers
var requestUri = Rx.Observable.just('https://api.github.com/users');
var responseStream = Rx.Observable.create(function (observer) {
jQuery.getJSON(requestUri)
.done(function(response) { observer.onNext(response); }
.fail(function(jqXHR, status, error) { observer.onError(error); })
.always(function() { observer.onCompleted(); });
});
Promises
Promise.prototype.then(onFulfilled, onRejected)
Why not just use...
Guaranteed one callback called - fulfilled or rejected
Handlers are called asynchronously
Once promise is settled, it will always return that particular value
First class async value - can be chained with then
mouse.listenForClicks()
.then(checkClickableElement, nonClickableElement)
.then(sendClickToOtherParticipant, clickSendError);
Promises
are not perfect
How to cancel a promise?
What if I don't care about the return value?
bigImageButton.click
.then(renderBigImage, imageFetchError)
// How to cancel?
e.g. our click was actually a double-click and we don't care about the single-click return value
What if multiple values returned?
Observable is Promise++
var responseStream = Rx.Observable.create(function (observer) {
jQuery.getJSON(requestUri)
.done(function(response) { observer.onNext(response); })
.fail(function(jqXHR, status, error) { observer.onError(error); })
.always(function() { observer.onCompleted(); });
});
Promise is simply an observable with single emitted value
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
Coming back to Ajax request example...
var responseMetastream = requestStream
.map(function(requestUri) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUri));
});
We can treat the promise result as a collection
We can cancel the streams
return Rx.Observable.fromPromise(jQuery.getJSON(requestUri));
Let's say you want to have a stream of "double click" events
Show me the code!
Let's also say that 3 or more clicks count also count as double clicks
How would you do that in traditional
imperative
and
stateful
fashion?
Would observables make it easier?
var clickStream = Rx.Observable.fromEvent(button, 'click');
var multiClickStream = clickStream
.buffer(function() { return clickStream.throttle(250); })
.map(function(list) { return list.length; })
.filter(function(x) { return x >= 2; });
var clickStream = Rx.Observable.fromEvent(button, 'click');
var multiClickStream = clickStream
.buffer(function() { return clickStream.throttle(250); })
.map(function(list) { return list.length; })
.filter(function(x) { return x >= 2; });
What about memory leaks?
// subscribe
var mouseMoveHandler = function(e){ console.log(e) };
document.addEventListener('mousemove', mouseMoveHandler);
// unsubscribe on click
var mouseClickHandler = function(e){
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('click', mouseClickHandler);
};
// subscribe
var mouseMoveStream = Rx.Observable.fromEvent(document, 'mousemove')
.forEach(function(x) { console.log(x) });
// unsubscribe on click
Rx.Observable.fromEvent(document, 'click')
.take(1)
.subscribe(function() { mouseMoveStream.dispose(); })
Rx.Observable.fromEvent(document, 'mousemove')
.takeUntil(Rx.Observable.fromEvent(document, 'click'))
.forEach(function(x) { console.log(x) } );
Remember how we talked how streams can complete?
Coming back to the first example
var engage = (visitorId, hangUpButton, callback) => {
var engageError, engagementMedia, preloadedDOM,
tryStartEngagement = () => {
if (engageError) {
cancelEngagement(engageError);
}else if (media && preloadedDOM) {
callback(preloadedDOM, engagementMedia);
}
});
var prepareMedia = () => {
mediaDetector.detect((media, error) => {
engageError = error;
engagementMedia = media;
tryStartEngagement();
});
};
var preloadDOM = () => {
DOMpreloader.preload((dom, error) => {
engageError = error;
preloadedDOM = dom;
tryStartEngagement();
});
};
hangUpButton.addEventListener("click", () => { engageError = "Hanged up"; })
preloadDOM();
prepareMedia();
}
With observables it's easy
var preparedEngagements =
DOMpreloader
.preload()
.takeUntil(hangUpClicked)
.combineLatest(mediaDetector.detect(), (preloadedDOM, media) => { preloadedDOM, media })
.take(1)
preparedEngagements.subscribe(
({media, preloadedDOM}) => engagement.start(media, preloadedDOM),
error => cancelEngagement(error)
)
A short demo about Javascript
A lightning talk by Gary Bernhardt from CodeMash 2012
https://www.destroyallsoftware.com/talks/wat
Testing
- Excellent Synchronous testing
- Emulate time using Test Scheduler
- collectionAssert.assertEqual
- Works with any JS testing framework
Testing example
test('buffer should join strings', function () {
var scheduler = new Rx.TestScheduler();
var eventStream = scheduler.createHotObservable(
onNext(100, 'A'),
onNext(200, 'B'),
onNext(250, 'C'),
onNext(300, 'D'),
onNext(450, 'E'),
onCompleted(500)
);
var results = scheduler.startScheduler(
function () {
return eventStream
.buffer(function () {return input.debounce(100, scheduler);})
.map(function (b) { return b.join(','); });
},
{ created: 50, subscribed: 150, disposed: 600 }
);
collectionAssert.assertEqual(results.messages, [
onNext(400, 'B,C,D'),
onNext(500, 'E'),
onCompleted(500)
]);
});
Marble Testing(Rx 5 only)
var e1 = hot('----a--^--b-------c--|');
var e2 = hot( '---d-^--e---------f-----|');
var expected = '---(be)----c-f-----|';
expectObservable(e1.merge(e2)).toBe(expected);
If you love testing with diagrams
it.asDiagram('debounce')('should debounce events', () => {
var e1 = hot('-a--bc--d---|');
var e2 = cold( '--| ');
var expected = '---a---c--d-|';
var result = e1.debounce(() => e2);
expectObservable(result).toBe(expected);
});
Debugging
var clickStream = Rx.Observable.fromEvent(button, 'click');
var multiClickStream = clickStream
.buffer(function() { return clickStream.throttle(250); })
.do(function(click) { console.log(click); })
.map(function(list) { return list.length; })
.do(function(clickCount) { console.log(clickCount); })
.filter(function(x) { return x >= 2; });
- Lot's of other helpful tools
- Timestamps
- Stacks
- The "do" keyword
- Can also be used for side-effects - like logging
Observables everywhere
- App startup
- Real-time Communication between people
- Real-time peer-to-peer DOM manipulation
- Data access
- Animations
- View/model binding
Rx revolution
Not just JavaScript!
Java, .NET, Scala, Ruby, etc
We use Observables in Server side!
The big players are using it
Frameworks:
Angular 2, React-redux, CycleJS and others
Companies:
Netflix, Microsoft, Square, Asana, Google and others
Questions?
Refererences:
- https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 - Great tutorial to start out with
- https://www.youtube.com/watch?v=XRYN2xt11Ek - Amazing speech by Matt Marenghi from Netflix + classical autocomplete example
- http://queue.acm.org/detail.cfm?id=2169076 - Your mouse is a database by Erik Meijer
- http://reactivex.io/documentation/operators.html - Official documentation with helpful images
- http://staltz.com/how-to-debug-rxjs-code.html - Testing and debugging examples
deck
By Stenver Jerkku
deck
- 5,231