Reactive JavaScript

@MichalZalecki

woumedia.com

reactive

\rē-ˈak-tiv\

done in response to a problem or situation

 merriam-webster.com

reactive programming

programming paradigm oriented around data flows and the propagation of change

 wikipedia.org/wiki/Reactive_programming

...but why should I care?

RP is a problem solver

and because you
have problem

because you we
have problem

async is hard

everything on front-end is async

...because user is async

Which problems RP solves?

  • data manipulation
  • events handling
  • errors handling
  • callback hell
  • promise hell
  • web workers
  • sockets
  • managing updates
  • race conditions
  • complex state
  • state dependencies
  • memory leaks

and more

but... how?

Observer pattern

Iterator pattern

Functional programming

+

ReactiveX

my very scientific chart

async awesomeness

promises

await/async

RxJS

function usersLanguages(username) {
  return fetch(`https://api.github.com/users/${username}`)
    .then(response => response.json())
    .then(data => fetch(data.repos_url))
    .then(response => response.json())
    .then(repos => Object.keys(
      repos.map(repo => repo.language)
        .filter(lang => !!lang)
        .reduce((akk, lang) => ({ ...akk, [lang]: null }), {})
    ));
}

usersLanguages("MichalZalecki").then(::console.log);

// ["JavaScript", "CoffeeScript", "CSS", "HTML", "Ruby", "Java"]
async function usersLanguages(username) {
  const user = await fetch(`https://api.github.com/users/${username}`);
  const userJSON = await user.json();
  const userRepos = await fetch(userJSON.repos_url);
  const userReposJSON = await userRepos.json();
  return Object.keys(
    userReposJSON.map(repo => repo.language)
      .filter(lang => !!lang)
      .reduce((akk, lang) => ({ ...akk, [lang]: null }), {})
  );
}

usersLanguages("MichalZalecki").then(::console.log);

// ["JavaScript", "CoffeeScript", "CSS", "HTML", "Ruby", "Java"]
function usersLanguages(username) {
  return Rx.DOM.ajax(`https://api.github.com/users/${username}`)
    .flatMap(({response}) => Rx.DOM.ajax(JSON.parse(response).repos_url))
    .flatMap(({response}) => JSON.parse(response))
    .map(repo => repo.language)
    .filter(lang => !!lang)
    .distinct();
}

usersLanguages("MichalZalecki").subscribe(::console.log);

// ["JavaScript", "CoffeeScript", "CSS", "HTML", "Ruby", "Java"]

promises

await/async

RxJS

callbacks

async awesomeness

Observer pattern

Iterator pattern

Functional programming

+

ReactiveX

Single Multiple
Pull Object Iterable
Push Promise Observable
import Rx from "rxjs";

const obs$ = Rx.Observable.create(observer => {
  observer.next(1);
  observer.next(2);
  observer.next(3);
});

obs$.subscribe(::console.log);

// 1 2 3
import Rx from "rxjs";

const obs$ = Rx.Observable.create(observer => {
  observer.next(1);
  observer.next(2);
  observer.error("Boom!");
  observer.next(3);
});

obs$.subscribe(::console.log, ::console.error);

// 1 2 "Boom!"
import Rx from "rxjs";

const obs$ = Rx.Observable.create(observer => {
  observer.next(1);
  observer.next(2);
  observer.error("Boom!");
  observer.next(3);
});

const obs2$ = Rx.Observable.of(100)

Rx.Observable.onErrorResumeNext(obs$, obs2$)
  .subscribe(::console.log, ::console.error);

// 1 2 100
import Rx from "rxjs";

const obs$ = Rx.Observable.create(observer => {
  observer.next(1);
  observer.next(2);
  observer.error("Boom!");
  observer.next(3);
});

const obs2$ = Rx.Observable.of(100);

obs$.catch(err => obs2$)
  .subscribe(::console.log, ::console.error);

// 1 2 100
import Rx from "rxjs";

const obs$ = Rx.Observable.create(observer => {
  observer.next(1);
  observer.next(2);
  observer.complete("Done!");
  observer.next(3);
});

obs$.subscribe(
  ::console.log,
  ::console.error,
  ::console.info
);

// 1 2 undefined
import Rx from "rxjs";

const obs$ = Rx.Observable.create(observer => {
  let i = 1;
  const tid = setInterval(() => {
    observer.next(i++);
  }, 1000);
  return () => clearInterval(tid);
});

const subscription = obs$.subscribe(::console.log);

setTimeout(() => subscription.unsubscribe(), 3000);

// 1..2..3
import Rx from "rxjs";

const obs$ = Rx.Observable
  .create(function subscribe(observer) {

      return function unsubscribe() {};
  });
import Rx from "rxjs";

const obs$ = Rx.Observable.create(observer => {
  observer.next(1);
  observer.next(2);
  observer.next(3);
});

console.log("before");
obs$.subscribe(::console.log);
console.log("after");

// "before" 1 2 3 "after"
import Rx from "rxjs";

const obs$ = Rx.Observable.interval(1000);

obs$.subscribe(x => console.log(`A: ${x}`));
setTimeout(() => {
  obs$.subscribe(x => console.log(`B: ${x}`));
}, 3000);

// A: 0
// A: 1
// A: 2
// B: 0
// A: 3
// B: 1
import Rx from "rxjs";

const obs$ = Rx.Observable.interval(1000).share();

obs$.subscribe(x => console.log(`A: ${x}`));
setTimeout(() => {
  obs$.subscribe(x => console.log(`B: ${x}`));
}, 3000);

// A: 0
// A: 1
// A: 2
// B: 2
// A: 3
// B: 3

Observer pattern

Iterator pattern

Functional programming

+

ReactiveX



const iterable = [1, "3", "xyz", 6];

iterable
  .forEach(x => console.log(x));

// 1 "3" "xyz", 6
import Rx from "rxjs";

const iterable = [1, "3", "xyz", 6];

Rx.Observable.from(iterable)
 .subscribe(x => console.log(x));

// 1 "3" "xyz", 6


const iterable = [1, "3", "xyz", 6];

const result = iterable
 .map(x => parseInt(x))
 .filter(x => !!x)
 .reduce((akk, x) => akk+x, 0);

console.log(result);

// 10
import Rx from "rxjs";

const iterable = [1, "3", "xyz", 6];

const obs$ = Rx.Observable.from(iterable)
 .map(x => parseInt(x))
 .filter(x => !!x)
 .reduce((akk, x) => akk+x, 0);

obs$.subscribe(::console.log);

// 10
import Rx from "rxjs";

const iterable = [1, "3", "xyz", 6];

const obs$ = Rx.Observable.from(iterable)
 .map(x => parseInt(x))
 .filter(x => !!x)
 .scan((akk, x) => akk+x, 0);

obs$.subscribe(::console.log);

// 1 3 10
import Rx from "rxjs";

const iterable = [1, "3", "xyz", 6];

const obs$ = Rx.Observable.from(iterable)
 .map(x => parseInt(x))
 .filter(x => !!x)
 .do(::console.info)
 .scan((akk, x) => akk+x, 0);

obs$.subscribe(::console.log);

// 1 1 3 4 6 10
import Rx from "rxjs";

const iterable = new Set([1, 1, "3", "xyz", 6]);

const obs$ = Rx.Observable.from(iterable)
 .map(x => parseInt(x))
 .filter(x => !!x)
 .scan((akk, x) => akk+x, 0);

obs$.subscribe(::console.log);

// 1 3 10
import Rx from "rxjs";

const promise = Promise.resolve("meet.js");

const obs$ = Rx.Observable.fromPromise(promise);

obs$.subscribe(::console.log);

// "meet.js"
import Rx from "rxjs";

const obs$ = Rx.Observable
  .fromEvent(document, "mousemove");

obs$.subscribe(::console.log);

// MouseEvent..MouseEvent..MouseEvent..
import Rx from "rxjs";
import "rxjs/add/observable/dom/webSocket";

const obs$ = Rx.Observable
  .webSocket("ws://localhost:8081");

obs$.subscribe(::console.log);

// message..message..message...

Observer pattern

Iterator pattern

Functional programming

+

ReactiveX

operators...

...but you don't need them all

seriously

The most useful*

  • map
  • filter
  • flatMap
  • concatMap
  • reduce
  • scan
  • do
  • retry
  • catch
  • zip
  • groupBy

* speaking from my experience

  • take
  • skip
  • publish
  • share
  • shareReplay (pushReplay + refCount)
  • withLatestFrom
  • debounce/debounceTime
  • buffer
  • delay
  • pluck
  • distinct/distinctUntilChanged

"Rx is lodash or underscore for async"

Cycle.js

import Cycle from "@cycle/core";
import {makeDOMDriver} from "@cycle/dom";

function main(sources) {
  const actions = intent(sources);
  const state$ = model(actions);
  const sinks = {
    DOM: view(state$),
  };
  return sinks;
}

const drivers = {
  DOM: makeDOMDriver("#root"),
};

Cycle.run(main, drivers);

Reactive JavaScript

By Michał Załęcki

Reactive JavaScript

  • 4,107