Functional Reactive Programming

WTF is FRP?

Reactive programming is programming with asynchronous data streams.

- Andre Staltz, creator of cycle.js

Streams, the missing piece

Everything can be a stream

  • Events
  • Variables
  • Data structures

You can actually model every input/output that happens in a browser as a stream (or a stream of streams)

But, can we, effectively, build a program using this notion?

The concept of stream is a good fit for our domain problem, but we need a way of creating, operating, subscribing and reacting to new values emitted by them.

var arr = [1, 2, 3, 4];

Streams are a lot like async JS arrays

1

2

3

4

Meet your new friend: The Observable

The first thing we need to understand is that observables are not streams. Actually, you can better understand the observable/stream relation if you think about observables as APIs to interact with a stream.

 

Observables also allow us to operate over streams with a set of functional operators.

Your friendly neighbor:

RxJS

Reactive Extensions for JS

Are a set of libraries for composing asynchronous and event-based programs using observable sequences and fluent query operators that many of you already know by Array#extras in JavaScript

- RxJS github repo

Reactive Extensions

  • Provides an API to work with streams
  • Implements the observable primitive 

Before going any further:

Basic RxJS implementation

RxJS operators

var source = Rx.Observable.range(0, 3)
    .map(function (x) { return Rx.Observable.range(x, 3); })
    .switch();

var subscription = source.subscribe(
    function (x) {
        console.log('Next: ' + x);
    },
    function (err) {
        console.log('Error: ' + err);
    },
    function () {
        console.log('Completed');
    });

switch

Convert an Observable that emits Observables into a single Observable that emits the items emitted by the most-recently-emitted of those Observables.

var source1 = Rx.Observable.of(1,2,3);
var source2 = Rx.Observable.of('foo', 'bar');
var source3 = Rx.Observable.of(4,5,6);

var merged = Rx.Observable.merge(source1, source2, source3);

var subscription = merged.subscribe(
  function (x) { console.log('Next:' + x); },
  function (err) { console.log('Error:' + err); },
  function () { console.log('completed') } 
);

merge

Combine multiple Observables into one by merging their emissions.

var source = Rx.Observable.timer(0, 1000)
    .takeUntil(Rx.Observable.timer(5000));

var subscription = source.subscribe(
    function (x) { console.log('Next: ' + x); },
    function (err) { console.log('Error: ' + err); },
    function () { console.log('Completed'); });

takeUntil

Discard any items emitted by an Observable after a second Observable emits an item or terminates.

var source = Rx.Observable.range(1, 5)
    .takeWhile(x => x < 3);

var subscription = source.subscribe(
    function (x) { console.log('Next: ' + x); },
    function (err) { console.log('Error: ' + err); },
    function () { console.log('Completed'); });

takeWhile

Mirror items emitted by an Observable until a specified condition becomes false.

var source = Rx.Observable.range(1, 3)
    .scan((acc, x) => acc + x);

var subscription = source.subscribe(
    function (x) { console.log('Next: ' + x); },
    function (err) { console.log('Error: ' + err); },
    function () { console.log('Completed'); });

scan

Apply a function to each item emitted by an Observable, sequentially, and emit each successive value.

var source = Rx.Observable.range(1, 3)
    .mapTo(x => x + 10)
    .startWith(5)
    .scan((acc, x) => x(acc));

var subscription = source.subscribe(
    function (x) { console.log('Next: ' + x); },
    function (err) { console.log('Error: ' + err); },
    function () { console.log('Completed'); });

mapTo

Maps every value to the same value every time.

var source = Rx.Observable.range(1, 3)
    .startWith(5)
    .scan(
        function (acc, x) {
            return acc + x;
        });

var subscription = source.subscribe(
    function (x) { console.log('Next: ' + x); },
    function (err) { console.log('Error: ' + err); },
    function () { console.log('Completed'); });

startWith

Emit a specified sequence of items before beginning to emit the items from the source Observable.

// Using a function
var source = Rx.Observable.range(1, 3)
    .map(function (x, idx, obs) {
        return x * x;
    });

var subscription = source.subscribe(
    function (x) { console.log('Next: ' + x); },
    function (err) { console.log('Error: ' + err); },
    function () { console.log('Completed'); });

map

Transform the items emitted by an Observable by applying a function to each item.

var source = Rx.Observable.range(0, 5)
  .filter(function (x, idx, obs) {
    return x % 2 === 0;
  });

var subscription = source.subscribe(
  function (x) { console.log('Next:' + x); },
  function (err) { console.log('Error:' + err); },
  function () { console.log('Completed'); });

filter

Emit only those items from an Observable that pass a predicate test.

var source = Rx.Observable.range(1, 3)
    .reduce((acc, x) => acc * x)

var subscription = source.subscribe(
    function (x) { console.log('Next: ' + x); },
    function (err) { console.log('Error: ' + err); },
    function () { console.log('Completed'); });

reduce

Apply a function to each item emitted by an Observable, sequentially, and emit the final value.

var source1 = Rx.Observable.interval(100)
    .map(function (i) { return 'First: ' + i; });

var source2 = Rx.Observable.interval(150)
    .map(function (i) { return 'Second: ' + i; });

var source = source1.combineLatest(
        source2,
        function (s1, s2) { return s1 + ', ' + s2; }
    ).take(4);

var subscription = source.subscribe(
    function (x) {
        console.log('Next: ' + x.toString());
    },
    function (err) {
        console.log('Error: ' + err);
    },
    function () {
        console.log('Completed');
    });

combineLatest

When an item is emitted by either of two Observables, combine the latest item emitted by each Observable via a specified function and emit items based on the results of this function

References and further reading

Functional Reactive Programming

By Carlos Vega

Functional Reactive Programming

  • 427