Functional programming and reactive programming principles are not new to JavaScript, but their adoption has recently become widespread across most modern frameworks and toolkits. The ease of using these approaches has improved as we’ve finally seen the decline of legacy browsers, and as we’ve seen the introduction of functional and reactive paradigms within ES6 and ES8.
First, let’s quickly review the emerging patterns in the current state of JavaScript.
Functional programming
Functional programming is a collection of techniques to create code that does not introduce side effects. At its foundation is the concept of pure functions, where their return value is determined only by its arguments and it does not mutate those arguments or anything outside that function in the process. JavaScript has added a lot of functional patterns over the years, most prominently APIs for working with arrays and iterators.
Data within functional programming should be immutable. On the simplest level, if an object can have its value modified after creation it is mutable. JavaScript is generally a very mutable language, and it takes a fair amount of care and effort to ensure that the right portions of your source code are immutable, and these approaches form the basis for significant portions of modern JavaScript frameworks.
Functional programming also expects that things are stateless. In general, state refers to the information that is available and may be operated on at a particular point in time by your source code. Stateful code cares about the current state, whereas Stateless code performs operations as if they are being run for the first time at all times. A pure function is by its nature stateless. Stateless applications do still manage state which leads to a fair amount of confusion, but they can return their state without mutating their previous state. So think of this as each state in time is a new representation of the current state rather than attempting to rewrite the old state into a new state.
To work with all of this, there’s often some form of container or controller code that will manage side effects and work across states, keeping the remainder of your code pure and free of side effects.
Reactive programming
Reactive programming is an event-driven paradigm, which publishes and listens to events over time in a declarative manner. It took the JavaScript community more than a decade to really get reactive programming working reasonably well. Consider some of the things that were tried along the way:
- function calls for getters/setters (Dojo 1 approach), to wrap any property of interest in an extra function call so that it could be tracked
- watch, an early but poorly performing attempt in Firefox at watching property changes
- MutationEvents, an early W3C DOM standard for tracking all DOM changes that was too slow to reliably use
- ES6 native getters/setters, an important building block
- MutationObservers, a faster way of tracking DOM changes, but still limited to the DOM
- Object.observe and its demise, early versions of Dojo 2 had a shim with improvements
- RxJS Observables which is very nice, but RxJS does a lot more than Observables which you may or may not need
- ES8 Observables
RxJS has popularized reactive programming in JavaScript, and one of its main features, Observables, are now part of ES8 and Dojo 2 provides a shim for the Observable API.
Observables operate like arrays that occur asynchronously over time. The ES8 implementation of Observables may be thought of a simpler version of streams. With both Observables and streams, source code can observe changes over time, and almost anything can be made into an Observable or a Stream. It may help to think of a Stream or Observable as many Promises emitted over time.
Functional reactive programming
Now, when you combine the power of functional and reactive programming together with a modern framework and set of APIs, you move beyond the world of previous tools and frameworks and into the benefits you get with React+ Redux or Dojo 2. Simply stated, functional reactive programming gives us declarative and side effect free source code that works efficiently as values change over time. This means that functional reactive programming is dynamic by default with reduced complexity, and it can move between states and manage state changes efficiently.
Dojo 2
While many of these concepts existed in various forms in Dojo 1.x, Dojo 1 was not a purely functional reactive programming environment. It did a solid job of trying to advance these concepts, but these have been substantial improvements to JavaScript, browsers, and general thought around how to efficiently achieve this with JavaScript and/or TypeScript.
Dojo 2 takes these concepts as the foundation for how we write source code and applications. For example, look at an early example of using a portion of the @dojo/stores package:
const baseStore = createQueryStore({
data: [
{ id: 'item-1', foo: 2, bar: 'a' },
{ id: 'item-2', foo: 1, bar: 'b' }
]
});
const viewOne = createQueryStore<{id: string; foobar: string }>();
const viewTwo = createQueryStore<{ value: number, id: string }>();
viewOne.observe().subscribe((storeDelta) => {
console.log('View one');
console.log(storeDelta.afterAll);
});
viewTwo.filter((item) => item.value <= 10).sort('value').observe().subscribe((storeDelta) => {
console.log('View two');
console.log(storeDelta.afterAll);
});
materialize({
source: baseStore.transform((item) => ({ id: item.id, foobar: `${item.foo}-${item.bar}` }), 'id'),
target: viewOne
});
materialize({
source: baseStore.transform((item) => ({ id: item.id, value: item.foo }), 'id'),
target: viewTwo
});
baseStore.add([
{ id: 'new-item', foo: 11, bar: 'c' },
{ id: 'new-item2', foo: 12, bar: 'd' }
])
.then(() => baseStore.delete('new-item'))
.then(() => baseStore.put({ id: 'new-item2', foo: -1, bar: 'zero' }));
In this example, you’ll see how we can observe changes on a store with viewOne.observe()
, and then subscribe to those changes. We can then take a data set, filter the results, sort it, then observe any changes, and finally subscribe to any updates with viewTwo.filter((item) => item.value <= 10).sort('value').observe().subscribe(
.
The materialize API allows us to define a transformation of each item in our store, mapping the data from an original format to the desired format within our views.
All of this together combines some of the standard patterns of functional reactive programming. This approach is found within several parts of Dojo 2, in particular within anything that tracks changes in data or properties such as widgets or data stores.
The above example outputs the following:
View one
[]
View two
[]
View one
[ { id: 'item-1', foobar: '2-a' },
{ id: 'item-2', foobar: '1-b' } ]
View two
[ { id: 'item-2', value: 1 }, { id: 'item-1', value: 2 } ]
View one
[ { id: 'item-1', foobar: '2-a' },
{ id: 'item-2', foobar: '1-b' },
{ id: 'new-item', foobar: '11-c' },
{ id: 'new-item2', foobar: '12-d' } ]
View one
[ { id: 'item-1', foobar: '2-a' },
{ id: 'item-2', foobar: '1-b' },
{ id: 'new-item2', foobar: '12-d' } ]
View one
[ { id: 'item-1', foobar: '2-a' },
{ id: 'item-2', foobar: '1-b' },
{ id: 'new-item2', foobar: '-1-zero' } ]
View two
[ { id: 'new-item2', value: -1 },
{ id: 'item-2', value: 1 },
{ id: 'item-1', value: 2 } ]
Achieving the above without functional reactive programming principles would require significantly more effort, would have additional synchronization challenges, and result in larger and more brittle source code.