Your browser doesn’t support the features required by impress.js to do its thing, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome, Safari or Firefox browser.

State Your Business

A journey in the world of
Reactive Programming
with JavaScript

Reactive?

Seminal paper:
Functional Reactive Animation (Fran), Conal Elliott and Paul Hudak, 1997

Reactive & Me

Composer — Rich SPA in Angular

Plumber — Declarative build pipelines

Media service — Reactive Scala channels

Functional

“Treat computation as the evaluation of mathematical functions and avoid state and mutable data.”
“[Pure functions] produce results that depend only on their inputs and not on the program state.”

Avoid side-effects

No statements, only expressions

Program with values
not actions

Avoid side-effects: Immutable values

Don’t mutate: transform, combine, yield new values

Simple data types: collections, list, map

Function composition

Find length of longest Guardian URL in a list

var urls = [
  "http://www.theguardian.com/world/2014/apr/22/north-koreans-turning-against-the-regime",
  "http://www.dailymail.co.uk/news/article-2610685/Dont-ask-migrants-long-theyre-staying-Border-guards-told-quizzing-arrivals-EU-breaches-Brussels-rules.html",
  "http://www.theguardian.com/lifeandstyle/wordofmouth/2014/mar/10/quinoa-cappuccino-soy-almond-oat-milk-dairy-alternatives"
];
// Imperative
var maxGuUrlLen = 0;
for (var i = 0, l = urls.length; i < l; i++) {
  if (/^https?:\/\/www.theguardian.com/.test(urls[i])) {
    maxGuUrlLen = Math.max(maxGuUrlLen, urls[i].length);
  }
}

// Functional with composition
var maxGuUrlLen = urls.filter(function(s) {
  return /^https?:\/\/www.theguardian.com/.test(s);
}).map(function(s) {
  return s.length;
}).reduce(function(a, b) {
  return Math.max(a, b);
}, 0);
// Define helpers
var guardianRegex = /^https?:\/\/www.theguardian.com/;
var isGuardianUrl = guardianRegex.test.bind(guardianRegex);

function prop(name) {
  return function(obj) {
    return obj[name];
  };
}

function max(a, b /* ignore the rest */) {
  return Math.max(a, b);
}
// Imperative
var maxGuUrlLen = 0;
var len = prop("length");
for (var i = 0, l = urls.length; i < l; i++) {
  if (isGuardianUrl(urls[i])) {
    maxGuUrlLen = max(maxGuUrlLen, len(urls[i]));
  }
}

// Functional with composition
var maxGuUrlLen = urls.
  filter(isGuardianUrl).
  map(prop("length")).
  reduce(max);

Function composition

More readable

More reusable

Separate logical tasks

Like Lego blocks, rather than Jenga tower

Declarative style

“A style of building the structure and elements of computer programs, that expresses the logic of a computation without describing its control flow.”

Express the result in terms of the inputs, rather than starting from the input and crafting your way to the result

Express what the program should do,
not how

Real World

Async

Some things take time

JavaScript is single-threaded: don’t wait!

Callbacks

> ajax(url, function(err, content) { ... });
// => undefined

Based on side-effects

var urls = [...];
urls.
  filter(isGuardianUrl).
  map(function(url) {
    ajax(url, function(err, content) {
      // ???
    });
  });

Not composable

ajax(url, function(err, content) {
  if (! err) {
    ajax(sequencedUrl, function(err2, content2) {
    });
  }
});

ajax(parallelUrl, function(err3, content3) {
});

No sequencing or concurrency

function getAsync(cb) {
  ajax(url, function(err, content) {
    if (err) return cb(err);
    // ...
  });
}

No automatic error propagation

Callbacks = fallback to imperative code

Promises

var url = "http://api.example.com/article/1";
var response = $http.get(url);
response.then(function(body) {
  console.log("received response: ", body);
}, function() {
  console.error("Oops, ajax failed");
});

Represent the eventual value (or error) over time

then()

Not a way to register callbacks

Transforms / maps the eventual value

var headline = response.then(function(body) {
  return body.field.headline;
});

Note: you can’t “extract” the value, you must receive it as part of a then()

flatMap
var authorName = response.then(function(body) {
  return body.author.id;
}).then(function(authorId) {
  return $http.get("/api/people/" + authorId);
}).then(function(author) {
  return author.name;
}, function(e) {
  console.log("epic fail:", e);
});
Error propagation
var authorName;
try {
  var authorId = body.author.id;
  var author = get("/api/people/" + authorId);
  authorName = author.name;
} catch(e) {
  console.log("epic fail:", e);
}

Promises guide us through the “happy path”

Declarative concurrency
var urls = [ ... ];
var requestPromises = urls.
  filter(isGuardianUrl).
  map(ajax);
var maxGuPageLen = Promise.all(requestPromises).
  then(function(pages) {
    return pages.map(prop("length")).reduce(max);
  });
Eventual state

Like async, but initiated externally

“Check or observe”
if (document.readyState === "complete") {
  doTheThing();
} else {
  document.addEventListener("readystatechange",
                            function observe() {
    if (document.readyState === "complete") {
      doTheThing();
      document.removeEventListener("readystatechange",
                                   observe);
    }
  });
}
var domReady = new Promise(function(resolve, reject) {
  if (document.readyState === "complete") {
    resolve();
  } else {
    document.addEventListener("readystatechange",
                              function observe() {
      if (document.readyState === "complete") {
        resolve();
        document.removeEventListener("readystatechange",
                                     observe);
      }
    });
  }
});
domReady.then(doTheThing);

// Elsewhere
domReady.
  then(loadComments).
  then(highlightComments);

Uniform interface for delayed values

Summary

It’s not about avoiding “callback hell”

Callbacks ➡ Promises

Libraries: when.js, rsvp, Q, Angular $q, Bluebird, etc.

Native in ES6,
already available in modern browsers,
soon in NodeJS

Mutable state

Like eventual state, but ever-changing

  1. Get current value
  2. Observe change event to update value
var queryEl = document.querySelector(".query");
var q = queryEl.value;
queryEl.addEventListener("input", function(ev) {
  q = queryEl.value;
});

More side-effects...

var q = queryEl.value;
var cleanQ = q.trim().toLowerCase();
var entries = matchEntries(cleanQ);
// ... more things with q...
queryEl.addEventListener("input", function(event) {
  q = queryEl.value;
  cleanQ = q.trim().toLowerCase();
  entries = matchEntries(cleanQ);
  // ... more things with q...
});

What about the code that uses entries?

Google Spreadsheet formula

Computed expressions

Anything that depends on a mutable state is itself mutable

Represent that dependency declaratively

Angular

$scope.firstName = "Conal";
$scope.lastName = "Elliot";

$scope.fullName = function() {
  return $scope.firstName + " " + $scope.lastName;
};
// Read current computed value
$scope.fullName(); // "Conal Elliot"

// Observe computed value for changes
$scope.$watch("fullName()", function(fullName) {
  console.log("Hello " + fullName);
});
// outputs "Hello Conal Elliot"

// Update source value
$scope.firstName = "Billy";
// outputs "Hello Billy Elliot"
// on next $digest cycle

Angular

Expression as a simple function

Must observe using $watch

Expensive dirty-checking
(until Object.observe)

$watch uses Object reference, need to use $watchCollection

Ember

App.Person = Ember.Object.extend({
  firstName: null,
  lastName: null,
  fullName: function() {
    return this.get("firstName") + " " +
           this.get("lastName");
  }.property("firstName", "lastName")
});

var person = App.Person.create({
  firstName: "Conal",
  lastName:  "Elliot"
});
person.get("fullName"); // "Conal Elliot"

App.Person = Ember.Object.extend({
  // ...

  fullNameChanged: function() {
    console.log("Hello " + this.get("fullName"));
  }.observes("fullName").on("init")
});

person.set("firstName", "Billy");
// outputs "Hello Billy Elliot"

Ember

Explicit deps

Only as part of Ember.Object class

Weird syntax for collections
.property("todos.@each.isDone")

Knockout

var firstName = ko.observable("Conal");
var lastName  = ko.observable("Elliot");

var fullName = ko.computed(function() {
  return firstName() + " " + lastName();
});
fullName(); // "Conal Elliot"

fullName.subscribe(function(fullName) {
  console.log("Hello " + fullName);
});
// outputs "Hello Conal Elliot"

firstName("Billy");
// outputs "Hello Billy Elliot"

Knockout

Simple type to wrap mutable values

Can be passed around like any value

Automatic dependencies

Caveat

Always synchronous

var friends = ko.computed(function() {
  asyncFindFriends(fullName(), function(err, ret){
    // ... ??
  });
  return // ... ??
});
var friends = ko.computed(function() {
  return promiseFindFriends(fullName());
});
// Now friends() returns a Promise...

var friendOfFriends = ko.computed(function() {
  return friends().then(function(frs) {
    return Promise.all(frs.map(function(f) {
      return promiseFindFriends(f);
    })).map(flatten);
  });
});
// Now friendOfFriends() returns a Promise too...

We need flatMap!

Observables

“The basic idea behind reactive programming is that there are certain data types that represent a value ‘over time’. Computations that involve these changing-over-time values will themselves have values that change over time.”

Bacon.js EventStream

// Given an EventStream `query'...
var matchingTitle = query.
  map(function(q){ return q.trim().toLowerCase();}).
  flatMap(function(q) {
    var request = reqwest({
      url: "http://content.guardianapis.com/search",
      type: "jsonp",
      data: {"page-size": 1, q: q}
    });
    return Bacon.fromPromise(request);
  }).map(function(resp) {
    return resp.response.results[0].webTitle;
  });
From DOM events
var query = Bacon.
  fromEventTarget(el, "input").
  map(".target.value");
From WebSockets
var query = Bacon.
  fromEventTarget(webSocket, "message").
  map(".data")
Filter
var matchingTitle = query.
  map(function(q){ return q.trim().toLowerCase();}).
  filter(function(q){ return q.length > 2; }).
  flatMap(function(q) { // ...
Sliding window
var lastTenMatchingTitles =
  matchingTitle.slidingWindow(10);
// Each value is an Array of titles
<button id="plus" >+</button>
<button id="minus">-</button>
<input type="text" id="number" />
var plusButton = document.getElementById("plus");
var minusButton = document.getElementById("minus");
var plus = Bacon.
  fromEventTarget(plusButton,  "click").map(1);
var minus = Bacon.
  fromEventTarget(minusButton, "click").map(-1);

var number = plus.merge(minus).
  scan(0, function(a,b) { return a + b });

var numberField = document.getElementById("number");
number.onValue(function(n) {
  numberField.value = n; // side-effect
});

RxJS Observable

Similar model, more modular

API similar to other languages (incl. Scala): Observable, Observers, Subjects, Schedulers, etc.

Querying of async data streams (LINQ)

Poor documentation

Highland Stream

Lazy streams, concurrency, backpressure

var filenames = highland(["foo.txt", "bar.txt", "baz.txt"]);
var extensions = filenames.map(function(filename){
  return filename.split(".")[1];
});
// doesn't actually map anything yet

extensions.each(function(extension) {
  console.log(extension);
}); // causes the stream to resume and output
Infinite streams
var i = 0;
var integers = highland(function(push, next) {
  push(null, i++);
  next();
});

integers.take(10).each(function(i) {
  console.log(i);
});
// Outputs 0, 1, 2, 3 ... 10
Concurrency
var readFile = highland.wrapCallback(fs.readFile);
//  readFile: String => Stream[Buffer]

var filenames = highland(["foo.txt", "bar.txt", "baz.txt"]);
var fileReaders = filenames.map(readFile);
// Stream[Stream[Buffer]] -- Nothing has run yet

var fileContents = fileReaders.parallel(10);
// Stream[Buffer] -- Nothing has run yet

filenames.observe().zip(fileContents).
  each(function(pair) {
    console.log(pair[0], ":", pair[1].toString());
  });

stream.parallel(n);

Consume n streams at a time, buffering to retain order.

stream.series();

Consume one stream at a time in order.

stream.merge();

Consume all streams at once, returning them as soon as they arrive (any ordering).

Delivery strategy

Sync (push)
BaconJS, RxJS, Node Streams v1

Simpler conceptually

Back-pressure (pull)
Highland, RxJS, Node Streams v2

Avoid flooding slow consumers by managing the source

UI

Both data input and output

Event-based MVC
e.g. Backbone

Render initial UI from model

Re-render UI on model change event

Update model on UI change event

Rendering the view

Backbone.View.extend({
  template: _.template(...),
  initialize: function() {
    this.listenTo(this.model, "change", this.render);
  },
  render: function() {
    var data = this.model.attributes;
    this.$el.html(this.template(data));
    this.$(".delete").toggle(user.isAdmin);
    return this;
  }
});

Not declarative, render as a side-effect

Expensive re-render, not targetted

Ideally, Viewt = f(Modelt)

Model ➞ View binding

<ul ng-repeat="task in tasks">
  <li ng-class="{'done': isDone() || expired}">
    {{name}}
  </li>
</ul>
<footer ng-show="tasks.length > 0">
  Total: {{tasks.length}}
</footer>

Declarative bindings (incl. expressions)

No imperative DOM manipulations

Update model on change

getInitialState: function() {
  return {salutation: "Hello!"};
},
handleChange: function(event) {
  this.setState({salutation: event.target.value});
},
render: function() {
  var value = this.state.salutation;
  return <input type="text" value={value}
                onChange={this.handleChange} />;
}

Side-effects, boilerplate

View ⇄ model binding

<input type="text" data-bind="value: salutations"/>

<script>
var model = {
  salutations: ko.observable()
};

ko.applyBindings(model);
</script>

Model value updated on view change

Two-way data-binding

Angular, Knockout, Ember, Ractive, Ripples, etc.

Bind computed or inline expressions into the view

Bind view value to a model value (bidirectional)

UI + Observables?

Bind Observable into the view

Bind view value to an Observable

Hybrid: angular-bacon, rx.angular

<input type="text" ng-model="username"/>
<input type="submit" ng-disabled="formValid"/>
// Controller:
var username = $scope.$watchAsProperty("username");
var password = $scope.$watchAsProperty("password");
var passwordIsValid = password.map(longerThan(5));
var usernameIsFree = username.changes().
  flatMapLatest(asyncCheckUsername).
  merge(username.map(false)).
  toProperty(false);
usernameIsFree.and(passwordIsValid).
  digest($scope, "formValid");

Reactive UI

Purely declarative

Including async and mutable state

Express the result in terms of the inputs

Summary

Reactive: extension of functional principles to include mutable state and time (async, events)

Observable data types to represent values over time and isolate side-effects

Reactive principles can be borrowed in a variety of contexts

Use a spacebar or arrow keys to navigate