Observables

Edit on GitHub

The name “Observable” immediately indicates a relation to the Observer pattern. This pattern is key in many flavors of the Model-View-Controller architectural pattern for user interfaces. For instance, typically the View observes changes in the Model. In the Observer pattern, this means the Model would be the “Subject” being observed.

However, an Observable is not exactly the same concept as a traditional Subject from the Observer pattern, because Observables share features with Iterables from the Iterator pattern as well.

Observables are lazy event streams which can emit zero or more events, and may or may not finish.

Observables originated from ReactiveX, a Reactive Programming library. Reactivity is an important aspect in Cycle.js, and part of the core principles that led to the creation of this framework. There is a lot of confusion surrounding what Reactive means, so let’s focus on that topic for a while.

Reactive Programming

Say you have a module Foo and a module Bar. A module can be considered to be an object of an OOP class, or any other mechanism of encapsulating state. Let’s assume all code lives in some module. Here we have an arrow from Foo to Bar, indicating that Foo somehow affects state living inside Bar.

modules Created with Sketch. Foo Bar

A practical example of such arrow would be: whenever Foo does a network request, increment a counter in Bar. If all code lives in some module, where does this arrow live? Where is it defined? The typical choice would be to write code inside Foo which calls a method in Bar to increment the counter.

// This is inside the Foo module

function onNetworkRequest() {
  // ...
  Bar.incrementCounter();
  // ...
}

Because Foo owns the relationship “when network request happens, increment counter in Bar”, we say the arrow lives at the arrow tail, i.e., Foo.

passive Created with Sketch. Foo Proactive Passive Bar

Bar is passive: it allows other modules to change its state. Foo is proactive: it is responsible for making Bar’s state function correctly. The passive module is unaware of the existence of the arrow which affects it.

The alternative to this approach inverts the ownership of the arrow, without inverting the arrow’s direction.

reactive Created with Sketch. Foo Listenable Reactive Bar

With this approach, Bar listens to an event happening in Foo, and manages its own state when that event happens.

// This is inside the Bar module

Foo.addOnNetworkRequestListener(() => {
  self.incrementCounter(); // self is Bar
});

Bar is reactive: it is fully responsible for managing its own state by reacting to external events. Foo, on the other hand, is unaware of the existence of the arrow originating from its network request event.

What is the benefit of this approach? Inversion of Control, mainly because Bar is responsible for itself. Plus, we can hide Bar’s incrementCounter() as a private function. In the passive case, it was required to have incrementCounter() public, which means we are exposing Bar’s internal state management outwards. It also means if we want to discover how Bar’s counter works, we need to find all usages of incrementCounter() in the codebase. In this regard, Reactive and Passive seem to be duals of each other.

  Passive Reactive
How does Bar work? Find usages Look inside

On the other hand, when applying the Reactive pattern, if you want to discover which modules are affected by an event in a Listenable module, you must find all usages of that event.

  Proactive Listenable
Which modules are affected? Look inside Find Usages

Passive/Proactive programming has been the default way of working for most programmers in imperative languages. Sometimes the Reactive pattern is used, but sporadically. The selling point for widespread Reactive programming is to build self-responsible modules which focus on their own functionality rather than changing external state. This leads to Separation of Concerns.

The challenge with Reactive programming is this paradigm shift where we attempt to choose the Reactive/Listenable approach by default, before considering Passive/Proactive. After rewiring your brain to think Reactive-first, the learning curve flattens and most tasks become straightforward, especially when using a Reactive library like RxJS.

RxJS Observables

Reactive programming can be implemented with: event listeners, Bacon.js, Kefir, EventEmitter, Actors, and more. Even spreadsheets utilize the same idea of the cell formula defined at the arrow head. The above definition of Reactive programming is not limited to RxJS, and does not conflict with previous definitions of Reactive Programming. RxJS was chosen as the reactive tool in Cycle.js.

Why RxJS?

Among JavaScript reactive programming libraries (often claiming to be “Functional Reactive Programming”), RxJS is a pioneer. It has inspired Bacon.js, which in turn has inspired Kefir. All major contenders to RxJS are inspired by it.

There is also an ongoing effort by the TC39 committee to insert the Observable into JavaScript (ECMA262), like Promise is in ES6. This would enable the future replacement of RxJS with a native Observable, making Cycle.js a very lightweight framework.

The precise definition of an Observable is given at ReactiveX.io documentation site. The Observable was first introduced in Rx.NET, then ported to JavaScript, Java, and other languages. Reactive Extensions, or ReactiveX, is the name of this collection of libraries across languages.

In short, an Observable is an event stream which can emit zero or more events, and may or may not finish. If it finishes, then it does so by either emitting an error or a special “complete” event.

Observable contract: (OnNext)* (OnCompleted|OnError){0,1}

As an example, here is a typical Observable: it emits some events, then it eventually completes.

completed-observable Created with Sketch. “onNext” Events onCompleted time

Observables can be listened to, just like EventEmitters and DOM events can.

myObservable.subscribe(
  function handleNextEvent(event) {
    // do something with `event`
  },
  function handleError(error) {
    // do something with `error`
  },
  function handleCompleted() {
    // do something
  }
);

Notice there are 3 handlers: one for events, one for errors, and one for “complete”. You can omit the last two in case you are only interested in handling “onNext” events.

RxJS Observables become very useful when you transform them with pure functions, creating new Observables on top of existing ones. Given an Observable of click events, you can make an Observable of “double-click” events.

const doubleClickObservable = clickObservable
  // collect an array of events after 250ms of event silence
  .buffer(() => clickObservable.debounce(250))
  // allow only arrays of length 2
  .map(arr => arr.length)
  .filter(x => x === 2);

Succinctness is Power, and RxJS operators demonstrate that you can achieve a lot with a few well-placed operators. These are only a few among the wide palette of available operators on Observables.

Knowing the basics of ReactiveX is a prerequisite to getting work done with Cycle.js. Instead of teaching RxJS on this site, we recommend a few great learning resources, in case you need to learn more:

Observables in Cycle.js

Now we are able to explain the types of senses and actuators, and what it means for the computer and human to be “mutually observed.”

In the simplest case, the computer generates pixels on the screen, and the human generates mouse and keyboard events. The computer observes these user inputs and the human observes the screen state generated by the computer. Notice that we can model each of these as Observables:

  • Computer’s output: a screen Observable.
  • Human’s output: an Observable of mouse/keyboard events.

The computer() function takes the human’s output as its input, and vice versa. They mutually observe each other’s output. In JavaScript, we could write the computer function as a simple chain of RxJS transformations on the input observable.

function computer(userEventsObservable) {
  return userEventsObservable
    .map(event => /* ... */)
    .filter(somePredicate)
    .flatMap(transformItToScreenPixels);
}

While doing the same with the human() function would be elegant, we cannot do that as a simple chain of RxJS operators because we need to leave the JavaScript environment and affect the external world. While conceptually the human() function can exist, in practice, we need to use driver functions in order to reach the external world.

Drivers are adapters to the external world, and each driver represents one aspect of external effects. For instance, the DOM Driver takes a “screen” Observable generated by the computer, and returns Observables of mouse and keyboard events. In between, the DOM Driver function produces “write” side effects to render elements on the DOM, and catches “read” side effects to detect user interaction. This way, the DOM Driver function can act on behalf of the user.

Joining both parts, we have a computer function, often called main(), and a driver function, where the output of one is the input of the other.

y = domDriver(x)
x = main(y)

The circular dependency above cannot be solved if = means assignment, because that would be equivalent to the command x = g(f(x)), and x is undefined on the right-hand side.

This is where Cycle.js comes in: you only need to specify main() and domDriver(), and give it to the Cycle.js run() command which connects them circularly.

function main(sources) {
  const sinks = {
    DOM: // transform sources.DOM through
         // a series of RxJS operators
  };
  return sinks;
}

const drivers = {
  DOM: makeDOMDriver('#app') // a Cycle.js helper factory
};

Cycle.run(main, drivers); // solve the circular dependency

This is how the name “Cycle.js” came to be. It is a framework that solves the cyclic dependency of Observables which emerge during dialogues (mutual observations) between the Human and the Computer.

What is Cycle Core?

Cycle Core delivers only the run() function which is implemented in about 100 lines of code. It’s a very small library.

In the TodoMVC built with Cycle.js, these are the proportions of code each library or section comprises:

  • RxJS: 71,1%
  • virtual-dom: 9,76%
  • @cycle/dom: 4,87%
  • src: 2,47%
  • @cycle/core: 0,64%
  • misc: 10,77%

Cycle.js is simply an architecture for building reactive web applications: a set of ideas about how you should structure your app using RxJS. To help you out, it also provides some libraries to address common use cases: Cycle DOM, to help interact with the DOM, and Cycle Core, to help create loops between the program and the drivers.

Read, next, the basic examples, which applies what we’ve learned so far about Cycle.js.