Blog

Reactive programming in Javascript

Author Image

Talha Aydin

Front-end Developer

24 okt 2018

Header image

User interaction with web-based software is becoming increasingly more dynamic thanks to modern front-end standards and libraries. Modern user interfaces require more complex thought and code in order to account for the asynchronous unpredictability of its different elements and user inputs. A real-time multi-user editing app, for example, has to be able to keep track of and handle all the inputs from multiple sources, while at the same time account for network errors, possible crashes, and so on.

Like any developer, I’ve been using callbacks, promises, async/await, and observables for asynchronous operations. While these work fine on their own, I’m really starting to like reactive programming for its simpler but more robust solutions. I’d like to share my own understanding and experiences in this blog post. I’ll explain what reactive programming is and how it can be used in Javascript by giving you an example with RxJS.

Reactive programming

Reactive programming is a paradigm that helps you think differently about the asynchronous state of your application. It says that any part of your application can, in essence, be represented as a collection of streams that emit data, and that complex behavior can be derived from the relationships between these streams.

It relieves you from the concern of state management and helps you concentrate only on application logic. You can basically build a layer of streams on top of your app, which gives you a standardized way of dealing with any events emitted from the underlying layers. This greatly improves the reusability and maintainability of your application code, as you can now focus on streams instead of the implementation details beneath.

Streams

A stream is basically an observable open channel that can emit data indefinitely. It can be subscribed to -- observed -- in order to react to the data being emitted, handle any emitted errors, or clean up when the stream completes and closes.

Each stream represents a certain kind of data pattern within your application. For instance, one stream could represent the chat messages in a window, and therefore emit chat message data every time a new message is pushed from the server. Another stream could be more simple and represent a mouse press input from your mouse, and emit the ‘mousedown’ DOM event every time you press your left mouse button. The amount of streams, what each one represents and how complex they are is up to you. Reactive programming frameworks like RxJS (which we’ll cover later) offer a broad range of tools to create any stream.

Once you have a collection of streams as a basis, you can either subscribe to them with handler functions, or use them as a basis for new streams, depending on your needs. Going back to the chat message stream example, you could subscribe to it with a handler function that updates the chat window DOM every time a new message arrives. You could also derive from it another stream that does one extra thing: Buffer messages that arrive too quickly in succession and emit them in a dosed manner to give automated chat messages a more natural feel. An even more complex stream can be derived by merging it with another chat message stream (third chat participant), or by pausing the stream whenever another stream starts emitting, etc.

Promises and observables

So how do promises and observables fit within this picture? Promises (as well as  async/await) are suitable for handling a single, push-based piece of data. Observables can handle multiple. Both are tools that help developers create dynamic apps, but they’re very specific solutions to specific problems and they’re very localized within the code you write. Reactive programming doesn’t replace them, but provides a certain way of modelling events and how they relate to each other within your application.

Observables play a key role within reactive programming, because a stream is, in essence, an observable: It emits -- pushes -- data multiple times over a certain period of time, just like an observable does. That’s why anything that pushes data can be merged into a stream, and this includes Promises and DOM events as well.

RxJS

An easy way to get started with reactive programming is to use RxJS. RxJS is a reactive programming framework in Javascript that gives you a huge collection of tools to create and tweak specific streams.

In RxJS streams are simply called Observables, because they follow the Observable pattern. You can start by creating an Observable out of anything that can be represented as a stream of data, like arrays and objects, DOM events, Promises, etc. By default, each subscription gets its own separate stream thread, but streams can also be configured to have a single execution thread and be multicasted to multiple subscribers.

You can create a new stream out of an existing one by piping it through functions called operators. These are inspired by array functions like map and filter, but there are tons more. The new stream, for example, could filter out certain data emissions. Streams are immutable, so whenever you create a new one the original is still there.

Example: Simple drawing app

The demo for this example can be seen live at http://aydin.pro/demo/reactive and the github repository at https://github.com/TalhaAydin/reactive-demo.

Let’s say you’re building a graphical editing application and you want the user to be able to make lines by moving the mouse while holding the left mouse button down. We want a stream that emits a series of MouseEvents while the mouse is being dragged in this way across the drawing area. The offsetX and offsetY properties of the MouseEvent can then be passed on to the Canvas API to make drawings.

First, let’s prepare the necessary RxJS classes and functions. Here I’m using the globally imported RxJS bundle.

const { fromEvent, combineLatest, merge } = rxjs;

const { map, filter, flatMap, takeUntil, startWith } = rxjs.operators;

In order to be able to react to the users mouse actions as specified above, we first have to keep track of three mouse events: ‘mousedown’, ‘mousemove’ and ‘mouseup’. Let’s make three basic streams out of these events. We can use the ‘fromEvent’ function to create a stream emitting the specified events originating at the given event target.

const drawingArea = document.querySelector('#drawing-area')

const mouseDownObservable = fromEvent(drawingArea, 'mousedown')

const mouseUpObservable = fromEvent(drawingArea, 'mouseup')

const mouseMoveObservable = fromEvent(drawingArea, 'mousemove')

Every time the ‘mouseDownObservable’ stream emits a ‘mousedown’ event, we want to keep track of the subsequent ‘mousemove’ events so that we know the coordinates to draw along. We want to keep doing this until the user releases the mouse button and a ‘mouseup’ event is emitted.

So in terms of streams, it means the following: We’re creating a new stream called ‘mouseDrawObservable’ in which each value emitted from ‘mouseDownObservable’ is mapped onto a separate substream. This substream does exactly the same as ‘mouseMoveObservable’ except that it stops emitting as soon as ‘mouseUpObservable’ emits an event. But we don’t want to have a stream of substreams (2-dimensional). We just want a single one-dimensional stream we can subscribe to for the necessary coordinates. That’s why we use ‘flatMap’ instead of ‘map’. The ‘flatMap’ operator flattens the stream by merging the substreams into the main stream (see code below).

In addition to the ‘mousemove’ events, we’ll also need the coordinates of the triggering ‘mousedown’ events, so that we can tell the Canvas API where to start drawing. That’s why we also use the ‘startWith’ operator, passing along the ‘mousedown’ event. The substream will start by emitting this single value, before emitting any other data.

The resulting code looks like this:

const mouseDrawObservable =

   mouseDownObservable

       .pipe(

           flatMap(

               (mouseDownEvent) => mouseMoveObservable.pipe(

                 startWith(mouseDownEvent),

                 takeUntil(mouseUpObservable)

               )

           )

       )

 

All we have to do now is subscribe to this stream: We start drawing lines at the ‘mousedown’ coordinates, and extend the line to each subsequent ‘mousemove’ coordinate within the canvas.

const context = drawingArea.getContext('2d');

mouseDrawObservable.subscribe({

   next: x => {

       if (x.type === 'mousedown') {

           context.moveTo(x.offsetX, x.offsetY);

       } else if (x.type === 'mousemove') {

           context.lineTo(x.offsetX, x.offsetY);

           context.stroke();

       }

   }

})

We now have a total of 4 streams which we can represent in a diagram. The dashed arrows represent the timeline moving from left to right. The letters represent emitted data at some point within this timeline.

mouseDownObservable:    ---d-------d--------------------d-------->

mouseMoveObservable:    -mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm->

mouseUpObservable:      -------u---------------------u------u---->

mouseDrawObservable:    ---dmmm----dmmmmmmmmmmmmmmmmm---dmmm----->

d = mousedown event

m = mousemove event

u = mouseup event

I would recommend visualizing streams using diagrams like this, or using marble diagrams. It makes it easier to understand the relationship between streams and helps you work towards your target streams.

Conclusion

Reactive programming has a steep learning curve. It may be hard at first to think in terms of streams and stream relations, and to get to know the huge set of stream manipulation functions. But in the end it pays off through scalability. Streams make your application much easier to understand and manage.

Gerelateerde artikelen & blogs