Reactive Programming revolves around the use of a data structure representing asynchronous data, called observables or streams. You could compare them to Promises.
(Diagrams are courtesy of RxMarbles.com. The arrows represent time.)
Roughly speaking, promises represent a value that might not be known when they are first used, but might arrive any moment. Observables are similar, except that they represent the potential arrival of multiple values.
Observables are great for representing related values arriving asynchronously, such as characters entered into an input field.
These can be simple operators like
delay, that simply returns a new stream that produces the same values as the input stream, only with a slight delay.
The operators might use the actual values produced like
map, which returns a new stream that produces the values of the input stream after being passed through a given function.
They might even combine multiple streams like
merge, which returns a stream that produces all the values produced by its input streams.
One you get used to them, observables can greatly simplify dealing with asynchronous data. For example, consider a situation where you have two input fields, and you want to check whether both of them have a value. When you have observables
field2$ (convention is to denote observables by appending a
$), you can use RxJS to create a stream that emits a new boolean every time the form changes validity:
const isValid$ =
.map(fields => fields.every(field => field.length > 0));
(You can see it in action in this fiddle.)
This might be difficult to parse the first time you read it, but once it clicks, working with asynchronous data becomes far easier with observables in our toolbox. Note that the above approach easily scales to however many form fields you have.
An additional benefit is that it steers you away from a whole class of bugs. To illustrate: consider trying to do the above using a more traditional approach. We would likely create an event listener to check the validity of the form when the user enters input:
return document.getElementById('field1').value.length > 0 &&
document.getElementById('field2').value.length > 0;
We wouldn't be the first to attach the event listener to the final form field and be done with it. We fill in the form, see the validity update, and conclude that it works. After deployment, however, some users will fill out the form, and then go back to the first field and clear its contents. Surprise: our form will still claim that it's valid. We forgot that the event handler also had to be attached to the other form field.
Obviously, what with you being the perfect programmer, this wouldn't happen to you. But by using observable, such errors would be really easy to spot. When your less gifted colleague adds is tasked with adding a form field, there's only one place it needs to be added: the combined form stream. It will then just work, validation included, as the data flows directly from the form fields into the validation.
There is much more to Functional Reactive Programming than this, and fully wrapping your head around it might take some time. If you're interested, you might want to try Getting Started with RxJS.
If you're as excited about FRP as I am, you're in luck: it's already gaining a lot of headway.
Consider, for example, the combination of React and Redux I touched upon in my previous post. With Redux, you describe how your application's state evolves in response to user actions with a function of the form
(currentState, action) => newState. You then use React to describe what view that state should result in with a function of the form
state => view.
This is a very limited reactive system. In essence, all asynchronous input to your application will have to be converted to that one observable for actions. This can get quite awkward for asynchronous input that isn't exactly a user action, such as HTTP requests. This often leads to workarounds such as emitting multiple actions, e.g. one when a request is sent, and another when the response comes in. Several libraries have arisen that claim to make it easier, but all of them are bound to that single stream.
A Redux alternative called MobX is also gaining ground, providing an implementation of observables to model component state. I haven't looked into it too deeply yet, but it appears to distinguish itself from Redux mainly by enabling components' local state to be modelled as observables as well, whereas Redux only supports a single place to hold all your application state.
On the Angular side, the developers have decided to endorse and use RxJS for the major rewrite that they are currently completing. They're using it for HTTP requests, the planned router is using it as well, and you can pass them to your view templates for more efficient rendering. That said, user input is still handled through callbacks, and in practise, many Angular apps will likely apply none or few transformations on their observables before passing its values back to regular callback- and promise-based code.
The first steps toward wider adoption of FRP have been taken, and they don't seem to be stopping any day soon. So where will this bring us?
One framework that is worth looking into in this regard is Cycle.js. Cycle applications are conceptually simple: all they do is convert a series of input streams (sources, in Cycle terminology) to a series of output streams (sinks). These input streams are provided by what Cycle dubs drivers, and the output streams are fed back into those drivers. The values emitted to the output streams are descriptions of the side effects you want to happen, and the drivers actually execute them, looping potential results back into the input streams. These drivers are usually small, separate libraries tailor-made to perform a specific side-effect in response to incoming observables, and to deliver any potential outside values through observables as well. A consequence is that the app itself is side-effect free, making it easy to reason about and easy to test.
Let's illustrate this with a very simple example application.
To be able to show something to the user, we output a description of what we want the DOM to look like onto the DOM driver's output stream. The DOM driver will then apply those changes to the DOM.
Likewise, the DOM driver also provides input streams that contains user input. Which can, in turn, be a trigger to push new DOM descriptions onto the output stream and thus update the view.
User interaction is only one type of side effect though. Another commonly used driver is the HTTP driver. By pushing values onto the output stream to the HTTP driver, apps can send HTTP requests, and the accompanying responses will be pushed onto the corresponding input stream.
The DOM and HTTP drivers are just the tip of the iceberg. There are plenty more drivers imaginable, such as the history driver for the browser's History API, a storage driver for interacting with localStorage, and more. Of course, you can also write your own.
This work by Vincent Tunru is licensed under a Creative Commons Attribution 4.0 International License.