Exploring Reactive Programming

Recently we have been exploring a new style of programming – Reactive Programming.

Share

Reactive Programming is a functional approach to programming where you can subscribe to events, streams, observables (call them whatever you want) which your application can react to. This isn’t unfamiliar in Web Development. Think of a DOM Event Listener:

button.addEventListener('click', () => { 
  console.log('click');
});

We do things like this all the time. What makes Reactive Programming different is that anything can be observed and reacted to. An input, data change, event, click, network request.. anything! They can be merged, filtered, mapped or even react to each other. Microsoft maintains a library of Reactive Extensions that allow you to work in this paradigm. We will focus on their Javascript library – RxJS. However, these concepts can be transferred across their entire suite of Reactive Extensions.

When I first began learning about Reactive Extensions I began reading Microsoft’s documentation… and I didn’t understand any of it. Check out the first line of their docs:

Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs using observable sequences and LINQ-style query operators.

My head hurt after reading that. But, it’s not as complicated as it sounds! The hardest part for me was getting my brain to conceptualise the flow of a reactive program instead of an imperative approach. It was difficult to grasp. An application would usually pull data from a source. Whereas a reactive application is offered more information by subscribing to a stream (called an observable in Rx) and an update is pushed to it from a source.

Creating an Observable

The RxJS library comes with a toolkit of helpers. However, the official documentation can be hard to follow. I have found using Learn RxJS as a basic API reference to be helpful. Lets create a click event. We want to turn the click event into an observable stream. We can do this by using fromEvent.

const { fromEvent } = Rx.Observable; 

const button = document.querySelector('#button');
const clickObservable = fromEvent(button, 'click');

We can then subscribe to that stream – when the button is clicked an event is pushed to the subscription, allowing us to perform an action in reaction to the event.

clickObservable.subscribe(() => {
  console.log('click');
});

We could achieve the same result using a DOM Event Listener. So what’s the point? The benefit of using an observable isn’t clear yet. Here is an example that can hopefully get you thinking about why we might want to use an observable:

const { fromEvent } = Rx.Observable; 

const button = document.querySelector('#button');
const clickObservable = fromEvent(button, 'click').bufferCount(5);

clickObservable.subscribe(() => {
  console.log('clicked 5 times');
});

Now a subscription to the click event will only fire if the button is clicked five times consecutively. How would that look like if you were to implement it yourself? RxJS can act as an abstraction around your code which allow you to save time usually spent on the smaller details of your application.

Fetching Data

Today, modern JavaScript applications can have many real time events which can be difficult to manage. On top of this an event is likely to retrieve data via HTTP requests, adding further complexity to an application. RxJS can help simplify things. Lets take a look at how we can build a simple reactive application that retrieves a random beer using the Punk API.

Anything can be an observable, even an Ajax request – after all observables are just an asynchronous event stream. RxJS actually has it’s own ajax binding that turns a HTTP request into an observable:

const { ajax } = Rx.Observable;
const API = 'https://api.punkapi.com/v2/beers/random';

const apiObservable = ajax({url: API, method: 'GET'});

apiObservable.subscribe(response => {
  console.log(response);  
});

Just like we could subscribe to the buttons click event. We can also subscribe to the response from the API. The data is pushed to the subscription and then we can react to this event. If you were wanting to use a promise based HTTP client then it is possible to transform a promise into an observable using fromPromise.

One function which you will need to become familiar with is the map function. You may have came across this before when dealing with an array: array.map(a => a). Similar to this, in the case of an observable – map applies a function to each value and emits the output of that function to the outputted observable. We could use this to push only the data we need to the subscription rather than the whole network response:

const { ajax } = Rx.Observable;
const API = 'https://api.punkapi.com/v2/beers/random';

const apiObservable = ajax({url: API, method: 'GET'})  
  .map(res => res.response[0]);

apiObservable.subscribe(data => {
  console.log(data);  
});

Merging Observables

We are now successfully retrieving the data we need for our application on load. But we want to also retrieve new data when hitting a refresh button. Usually, I would create a function that handles fetching data from the API and call that function on page load, then again on click. However we’re not thinking imperatively. Let’s combine our two previous examples and merge them into one observable stream we can subscribe to. This means every time we want to fetch data, the data will be pushed to this subscription and we can react accordingly.

const { ajax, fromEvent } = Rx.Observable;
const API = 'https://api.punkapi.com/v2/beers/random';

const button = document.querySelector('#button');

const refreshObservable = fromEvent(button, 'click');

const apiObservable = () =>
  ajax({ url: API, method: 'GET' })
    .map(res => res.response[0])

const createStream = (fetchData, event) =>
  fetchData()
    .map(res => res)
    .merge(
      event.switchMap(fetchData)
    );

createStream( 
  apiObservable, 
  refreshObservable
).subscribe(data => 
   console.log(data),
);  

To merge two observables we have created a function that takes them both as parameters and uses the operator merge. Merge takes multiple observables and merge them into one, pretty self explanatory! The flexibility of this approach means that we could create the stream again, with an entirely different event.

An important thing to take note of is how we used a different variant of the map function – switchMap. Why not just use map? switchMap is a variant of flatMap. map actually projects a whole new observable meaning we would be subscribed to a stream of observables. This isn’t useful to us. Using flatMap the observable is automatically flattened for us so we can observe the data directly. The difference with using switchMap is that as well as flattening the observable, after each emission the previous is cancelled. Therefore, in this example, if the random button was clicked multiple times and the previous request to the API hadn’t finished, it would be cancelled. This data would never be pushed to our subscription meaning we will only receive the data from the latest click event. There are other variants of map that can be offer a level of abstraction around asynchronous events that would be time consuming and difficult to implement yourself.

Hot and Cold Observables

In our example we used cold observables. A cold observable will only begin to push values when the subscribe function is called. It is important to know that this is not always the case. An observable can also be hot. Hot observables are already producing values before the subscription. An easy way to think about this is to visualise a timer that counts upwards in seconds. Using a cold observable, the timer would begin counting from the moment of subscription, meaning the first value pushed to the subscription would be "1". On the other hand, using a hot observable, the timer would start counting the moment it was created. If we were to subscribe to it three seconds after it was created the first value pushed to the subscription would be "4".

What next?

The example we have created can be found here on CodePen. It takes it one step further and uses the subscription data to update the DOM.

Initially some of the concepts have been difficult for me to grasp. However, when using RxJS in a real application it has helped me to write more efficient, elegant code in a fraction of the time. If you are wanting to learn more, here are some resources that have helped me whilst learning about RxJS:


Interested in learning more about enterprise WordPress?
Get in touch with the team today