The Road to (Functional) Reactive Programming in JavaScript, Part 1

The Road to (Functional) Reactive Programming in JavaScript, Part 1

This is part one of a three-part series on the road to reactive programming in Javascript. This road is comprised of:

  • The path so far: JavaScript foundations
    • Event loop
    • Concurrency
    • Run to completion
  • The ongoing journey: Async strategies
    • Callbacks
    • Promises
    • Generators
    • async/await
  • The road ahead: Streams
    • Observer pattern
    • (Functional) Reactive Programming

Back in 2001 Douglas Crockford wrote an article about JavaScript on his website that’s been a classic ever since. It’s titled:

The World’s Most Misunderstood Programming Language

A lot has changed since 2001, especially in web development and the JS ecosystem. But, something that has remained unchanged for the most part is the lack of understanding of Javascript foundational concepts, even though you can now find official documentation, great books, and tons of JS advocates writing valuable articles.

That’s why writing yet another post on how JavaScript manages asynchrony can never hurt, in my opinion. In this post, my goal is to draw a path that can lead to managing asynchrony in JavaScript using a (functional) reactive approach. Take this term with a grain of salt, as Functional Reactive Programming is a whole different beast than where we’ll be heading, but we will go into that deeper.

Also, anecdotally, you can see how Crockford even mentioned the functional approach that JavaScript has towards some of the core concepts.

The path so far: JavaScript foundations

In my opinion, one of the reasons that JavaScript remains misunderstood is because many people start using it as a way to just jump in and do what they want to do right off the bat. In general, with other programming languages, you would first learn the syntax, core principles, the build system, etc.

JavaScript also helps in this regard since it contains concepts and syntax based on a variety of languages, it’s easily approachable to a broad audience of developers.

Since it’s easy to get things up and running, it may seem rather odd that a developer would dive deep into some of the internal concepts. While creating a full-scale program in another language requires a vast amount of knowledge, you can use JavaScript to a large extent while only scratching the surface; you don’t usually need to worry too much about what’s going on under the hood.

Because JavaScript can be used without understanding, the understanding of the language is often never attained.

Kyle Simpson

One of the most important and yet often misunderstood concepts of programming in a language like JavaScript is how to express and manipulate code behavior over a period of time. Here is where the term, asynchrony, which the Collins dictionary defines as the “failure to occur at the same time,” comes in.

Event loop

Despite allowing asynchronous code to run, i.e., that fails to occur at the same time, JavaScript itself has never had any direct notion of asynchrony built into it (at least until ES2015 features). The JavaScript engine just executes a single chunk of your program at any given moment, when asked.

This is the event loop, which is single-threaded by nature.

Where does the asynchrony come from then? Well, this engine doesn’t run in isolation. It runs inside a hosting environment, which is for most developers the typical web browser, or other types of devices or environments, like a server, through Node.js.

It’s this surrounding environment that’s scheduling “events”. Besides JavaScript code, all the other stuff which is done by browser/Node.js (AJAX request, rendering, event triggers, etc.) is run on separate threads in an asynchronous/non-blocking fashion.

So, when you make an AJAX request to fetch some data from a server, you set up the “response” code in a function (commonly called a “callback”), and the JS engine tells its intention to the hosting environment. Then, the browser, or hosting environment, waits for the response from the network, and when it has something to give you, it schedules the callback function to be executed by inserting it into the event loop.

JavaScript Event Loop

Concurrency

As we’ve seen, JavaScript bases its concurrency model on this “event loop”, which is quite different from models in other languages like C and Java. That said, and given that JavaScript is single-threaded in the same execution context, what are our options for concurrency? Is it possible to achieve parallelism in JavaScript?

The only concurrency option (at least with classic ES5 JavaScript) is pseudo-parallelism based on specific techniques that pops our function out of the queue. Its main idea is to unblock user interaction with the interface, but, at the same time, execute logic that needs to be executed. This approach is based on breaking execution into parts, leading to concurrency.

Concurrency is when two or more chains of events interleave over time, such that from a high-level perspective, they appear to be running simultaneously (even though at any given moment only one event is being processed).

In traditional parallelism, processes and threads execute independently and may execute simultaneously, on separate processors, or even separate computers, but multiple threads can share the memory of a single process.

Threaded programming is very tricky, because if you don’t take special care to prevent this interleaving and associated shared memory problems, you can get very surprising, nondeterministic behavior that frequently leads to headaches.

The event loop, by contrast, breaks its work into tasks and executes them in serial, disallowing parallel access and changes to shared memory. JavaScript never shares data across threads (unless until Web Workers API and the SharedArrayBuffer, which are a recently added special use case).

But that doesn’t mean JavaScript is always deterministic.

Run to completion

Because of JavaScript’s single-threading, the code inside of a function, including callbacks, is atomic, which means that once x() starts running, the entirety of its code will finish before any of the code in y() can run, or vice versa. This is called “run-to-completion” behavior.

A JavaScript program is broken up into chunks, where the first chunk runs now and the next chunk runs later, in response to an event. Whenever there are events to run, the event loop runs until the queue is empty.

At any given moment, only one event can be processed from the queue at a time. While an event is executing, it can directly or indirectly cause one or more subsequent events.

So we still have nondeterminism. But it’s at the function (event) ordering level, rather than at the statement ordering level, as it is with threads. In other words, there is somehow more determinism than with threads, but it’s not avoided, so we have to be careful about this.


In the second part of this article series, we’ll continue to the next phase of our journey: async strategies.


Sources and further reading:

Ensure the success of your project

47 Degrees can work with you to help manage the risks of technology evolution, develop a team of top-tier engaged developers, improve productivity, lower maintenance cost, increase hardware utilization, and improve product quality; all while using the best technologies.