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
async problems
By thinking differently about
This is the story how we solved
and removed
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();
}
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();
}
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();
}
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();
}
Let's find out together!
From http://www.reactivemanifesto.org/
Reactive system are:
Responsive, Resilient, Elastic, Message Driven
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.
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 ;-)
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:
is programming with asynchronous streams
Async streams are:
Mouse clicks
Events
??? what else can be a stream ?
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
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
Variables
User input
Properties
Caches
Data structures
etc.
Facebook infinite scroll is a stream just like mouse click events are
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
They can be Mapped
They can be Filtered
They can be Debounced
This is just the tip of the iceberg
A library that helps you do Reactive programming
RxJS - Reactive extensions for javascript
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(); });
});
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);
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?
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
Let's also say that 3 or more clicks count also count as double clicks
How would you do that in traditional
and
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; });
// 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?
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();
}
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 lightning talk by Gary Bernhardt from CodeMash 2012
https://www.destroyallsoftware.com/talks/wat
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)
]);
});
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);
});
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; });
Not just JavaScript!
Java, .NET, Scala, Ruby, etc
We use Observables in Server side!
Frameworks:
Angular 2, React-redux, CycleJS and others
Companies:
Netflix, Microsoft, Square, Asana, Google and others
Refererences: