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:

License: https://creativecommons.org/licenses/by-nc-sa/3.0/

deck

By Stenver Jerkku

deck

  • 2,535
Loading comments...