Functional reactive programming and Observables in JavaScript, TypeScript, and Dojo 2

By on February 27, 2017 7:45 am

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.


Need Help With Your JavaScript Architecture?

Let's Talk! Logo

Let’s talk about how we can help you with your next project.

Support Logo

Get help from SitePen Support, our fast and efficient solutions to JavaScript development problems of any size.

Workshops Logo

SitePen workshops are a fun, hands-on way to keep up with JavaScript development and best practices. Register for an online workshop, today!

Contact Us Logo

Have a question? We’re here to help! Get in touch and let’s see how we can work together.

Comments

  • Macrow Willson

    nice, thanks for the update! But it would be nice if your team could provide working packages, despite its alpha. Most dojo2 packages fail when installed via npm install @dojo/… with “unmet peer dependencies….”. That makes it pretty impossible to start adopting at least any of the dojo2 packages. I am trying every week, sometime something works, but something else not! It also doesn’t matter its installed from git or npm….

  • Anthony Gubler

    Hi @macrowwillson:disqus thank you for your interest in Dojo 2. As things are moving pretty quickly with alpha it has been hard to keep all the peer dependencies aligned across the packages!

    Having said that, at the moment the main packages should be installable without the “unmet peer dependencies” warnings. The packages I installed are:

    “`
    npm install @dojo/has
    npm install @dojo/shim
    npm install @dojo/core
    npm install @dojo/i18n
    npm install maquette@2.4.3
    npm install @dojo/widget-core
    “`

    To make life even easier, you could install the `@dojo/cli` which comes with the `@dojo/cli-create-app` installed that allows you to create a skeleton application with the latest set of Dojo 2 dependencies using `dojo create app –name my-app`.

    I hope that helps, please let us know if you have any other questions.

  • Macrow Willson

    thanks a dozen but no, try yourself. for instance @dojo/core would abort with unmet deps (node7.4/ ts 2.2.1). however, i understand that npm versions could get messy at this early stage. that’s why I really hope you would remove peer deps all together and allow us npm install github.com/dojo/core.git … then, at least we could use your codebase directly without all the crap on in between: typings/npm/peer-deps. anyway, i will keep trying every week. our entire production got stuck since 8 months :-)

  • Anthony Gubler

    Are you just trying to install @dojo/core? Because you will get the warnings if you haven’t installed the peer dependencies that it requires.

    Today I was able to install, @dojo/core without any warnings after I had installed the it’s peer dependencies (has/shim).

    With npm 3+ which is required for Dojo 2 peer dependencies do not get automatically installed.

  • Macrow Willson

    and thanks for the cli tool pointer, but also there: it doesn’t allow me to actually use dojo2 directly or start building our foundation code on it (consuming your TS modules/code). I really hope the cli tools are not mandatory (reason to divorce from dojo :-) because as said, dojo-2 is for me foundation code, I really need to know what’s going on under the hood.

  • I just did a clean install… you get warnings, but you just need to install them… e.g.

    $ npm install @dojo/shim
    tests@2.0.0-pre /Users/default/dojo/tests
    ├── UNMET PEER DEPENDENCY @dojo/has@2.0.0-alpha.8
    └── @dojo/shim@2.0.0-beta.10

    npm WARN tests@2.0.0-pre requires a peer of @dojo/has@2.0.0-alpha.8 but none was installed.
    npm WARN @dojo/shim@2.0.0-beta.10 requires a peer of @dojo/has@2.0.0-alpha.8 but none was installed.

    $ npm install @dojo/has
    tests@2.0.0-pre /Users/default/dojo/tests
    └── @dojo/has@2.0.0-alpha.8

    $ ls
    node_modules package.json

    $ cd node_modules/
    $ ls

    @dojo

    $ cd @dojo
    $ ls
    has shim

    $ npm install @dojo/core
    tests@2.0.0-pre /Users/default/dojo/tests
    └── @dojo/core@2.0.0-alpha.25

    core has shim

  • Macrow Willson

    yes, i actually wanted to go for adopting core first in a blank project. seems i got something entirely wrong about the peer deps. gonna try again :-) thanks

  • Macrow Willson

    vale, now things start making sense, ash on my head. thanks Dylan!

  • No problem, we just want this to work for you and others, so I’m glad that cleared things up. The main thing is that @dojo/has and @dojo/shim are really small focused things that @dojo/core needs. :)

  • Macrow Willson

    greatI So i guess I can try now a ‘source code’ layout, using dojo-2 direct from source like with git submodules. Having typings only or npm in between won’t work nice for me. thanks again. g