There’s a lot of excitement about reactive programming in the front-end community:

Reactive programming is everywhere, so it’s time we took a look at its primary concept: the Observable. What problems does it solve for us?

To answer this question, I will present three different metaphors that highlight situations that Observables can deal with elegantly. After this article, you should have a good idea of when to use Observables, and for what.

Metaphor 1: Observables as special Promises

A Promise is a data structure that represents a value that might not be immediately available. This is very useful when you want to perform some action as soon as that value does become available. An example use case is when you have made an HTTP request: at a certain moment, the response is going to come in, at which point you will want to perform some action. A Promise can then represent that eventual response.

A Promise could be described as such:

A long arrow pointing to the right. At the far end at the right-hand side, a circle labeled

The arrow represents the passage of time; at a certain point, the promise is resolved with the value 3.

What can Observables do for me?

A Promise is useful if you’re waiting for a single value, and want to perform an action when that value comes in. But what if you’re waiting for multiple values? For example, what if you’re waiting for user input, where you’re waiting for multiple keypresses that drip in one by one?

This is where Observables come in. They can be seen as Promises that can return multiple values:

A long arrow pointing to the right. On top of the arrow are, from left to right, three circles labeled

The above is an Observable that, at different moments in time, delivers the values 1, 2 and then 3. Some time after that, it “completes”: no more values are to be expected. This is indicated by the vertical line.

Metaphor 2: Observables as a Design Pattern

Design Patterns are best practice approaches to common problems in software design. For example, the Iterator pattern is a common approach to dealing with containers with multiple elements, e.g. lists of users. Your code can use an Iterator to get access to the container’s elements, without needing to care about the container’s implementation.

Schematic representation of an Iterator: a block of code with three arrows pointing to an Iterator. The arrows represent method calls to access elements from the container.

Likewise, the Observer pattern is a common approach when you have code that needs to become active when something happens elsewhere in the code. In this pattern, your code (the Observer) can register itself with a Subject, which will then notify it when the change has occurred.

Schematic representation of an Observer: a block of code (the Observer) with an arrow pointing to it from a Subject. The arrow represents a method call on the Observer to notify it of an update.

What can Observables do for me?

Whereas an Iterator is useful when you have a container with multiple elements readily available, an Observable shines when these elements might not yet be available. For example, when you need to call several REST APIs to collect all profile data related to a user, the different elements of the profile might arrive at different moments. Using an Observable, you can construct the full profile as the data rolls in.

Observables can be seen as a combination of the Iterator pattern and the Observer pattern. Whereas the Iterator is pull-based (your code “pulls” values out of the Iterator), an Observable is push-based: the Observable “pushes” values to your code as they arrive.

Schematic representation of an Observable: a block of code with three arrows pointing to it from an Observable. The arrow represents method calls on your code to provide it with new elements as they arrive.

Metaphor 3: Observables as a way around shared mutable state

Consider two functions that increment and decrement a counter, respectively:

let counter = 0;

function increment(){
  counter++;
}

function decrement(){
  counter--;
}

In this example, both functions (shared) can change (mutable) the value of counter (state). This setup has some disadvantages, the most important of which is that it can make your code hard to follow. For example, you cannot simply look at the increment function in isolation: you have to keep track of every line of code that refers to counter and keep in mind how they can affect the function’s code and vice versa. In the toy example above this is not a problem, but as your codebase grows, your code will become harder and harder to maintain.

A common approach to deal with this is to write your functions in a way that they do not depend on anything outside that function — so-called pure functions. Rather than relying on the application being in a certain state, everything the function needs will have to be passed in as an argument. This results in functions that look like this:

function increment(previousValue){
  return previousValue + 1;
}

function decrement(previousValue){
  return previousValue - 1;
}

The question then is: how we can know what the present value of the counter is, e.g. to show it to the user? You could store it in a variable which could then be updated like this:

let counter = 0;
counter = increment(counter);

…but then we’re back in the land of mutating state! If we want to show this value to the user, we still have to be aware of every line of code that references counter and might require updating the view.

What can Observables do for me?

Observables provide a way to deal with changing values without having to resort to shared mutable state. Instead, you explicitly describe the way the value can change using pure functions. So to take our use case of showing the counter value to the user, we could simply subscribe to an Observable that delivers the new value of the counter as it changes, and use that to update the view.

An application window showing a 3, the current value of the counter. An arrow pointing to the window is delivering the new values 2, 1 and 2, respectively.

Let’s say we have two buttons for incrementing and decrementing our counter, respectively:

<button type="button" id="increment">Increment</button>
<button type="button" id="decrement">Decrement</button>

An Observable delivering the value 1 everytime the Increment button would look like this:

const incrementClick$ =
  Rx.Observable.fromEvent(document.getElementById('increment'), 'click')
  .map(ev => 1);

Likewise for decrementing, but with the value -1. An Observable delivering counter values would then be constructed as such:

const counter$ =
  // Create an Observable delivering 1 and -1 on
  // their respective button clicks:
  Rx.Observable.merge(incrementClick$, decrementClick$)
  // Emit a 0 before any button is clicked:
  .startWith(0)
  // Then add whatever is emitted (1, -1 or 0) to
  // whatever we had before:
  .scan((accumulator, value) => accumulator + value);

You can then subscribe to that Observable to update the view with the latest counter value:

counter$.subscribe(counter => {
  document.getElementById('counter').innerHTML = counter.toString();
});

(You can view the full working example at JSFiddle.)

It should be noted that there are nothing but pure functions in the above example. There is no explicit variable holding the state, yet we are still able to display a dynamic counter value.

What’s next?

We’ve seen three different metaphors that helps us think about Observables:

  • As Promises that can return multiple values
  • As a Design pattern for containers that will have multiple elements, eventually
  • As a way to represent mutable values without requiring shared mutable state

These metaphors provide insight into how Observables can be useful. As a next step, I’d recommend reading up on what you can do with Observables, and how they can help you structure your application. Coincidentally, I’ve written another article that deals with exactly that!

License

This work by Vincent Tunru is licensed under a Creative Commons Attribution 4.0 International License.