Intro to functional reactive programming for advance JS web development
If you are a frontend or backend JavaScript developer who works on large and complex JavaScript applications and deals with a lot of code that responds to asynchronous data updates, user activities, and system activities, then it’s perhaps the best time to explore functional reactive programming (FRP), as it’s a time-saving, bug-preventing, easy-to-read, and modularized style of writing code. You don’t need to know any functional programming language or be a hardcore functional language programmer; rather, you just need to know the basics of functional programming. In this article, we will learn how to use FRP using Bacon.js, which is an FRP library for both frontend and backend JavaScript.
We’ll cover the following:
- Reactive programming in a nutshell
- Problems with writing reactive code in JavaScript
- Introduction to functional programming
- What FRP is
- The building blocks of FRP
- The advantages of FRP
- All the APIs provided by Bacon.js
Introduction to reactive programming
Before we get into FRP, we need to understand what it is. I will be explaining reactive programming with respect to JavaScript. The concept of reactive programming is the same in every programming language.
Reactive programming is writing code to look for asynchronous data updates, user activities, and system activities and propagate changes onto the dependent parts of the application. Reactive programming is not something new; believe it or not, you have already been doing reactive programming without realizing it. For example, the code you write to handle a button’s click event is reactive code. There are various approaches to reactive programming, such as event-driven, callback, promise patterns and FRP.
Not every snippet of asynchronous code we write is reactive code. For example, uploading analytics data to a server asynchronously after a page load is not reactive code. But uploading a file to a server asynchronously and displaying a message to the user after the upload is complete is reactive code because we are reacting to the completion of the file upload.
A more complex example of reactive programming is in the MVC architecture, where reactive programming is what reacts to a change in the model and updates the view accordingly, and vice versa.
Problems with writing reactive code
There are basically three patterns natively supported by JavaScript for writing reactive code: event-driven, callback, and promise.
Anyone who knows a bit of JavaScript is familiar with event-driven and callback patterns. Although these two patterns are the most popular way of writing reactive code, they make it difficult to catch exceptions and result in nested function calls, which make the code harder to read and debug.
Due to the problems caused by event-driven and callback patterns, ES6 (https:// www.packtpub.com/web-development/learning-ecmascript-6) introduced the promise pattern. The promise pattern makes the code look more like synchronous code, therefore making it easy to read and debug. The pattern also makes exception handling easier. A promise represents an asynchronous operation.
But the promise pattern has a problem, that is, a promise can be resolved only once. The promise pattern can only respond to a single activity or data update of an asynchronous operation. For example, if we make an AJAX request using a promise pattern, then we can handle only request success and failure activities and not the states of the request and response cycle, such as weather server connections that have been established and response headers received. Similarly, if we handle a user click activity using a promise pattern, then we can handle only the first click, not the ones occurring after it, because the promise gets resolved in the first click.
You may or may not be familiar with the promise pattern, so let’s look at some sample code of what a promise pattern looks like:
$http(“http://example.com/data.json”).then(function(){
//do something
}).then(function(){
//do something more here
}).then(function(){
//do something more here
}).catch(function(){
//handle error
})
Here, the $http() method makes an HTTP request asynchronously and returns a promise. The promise is resolved if the request is successful, and the callback passed to the first then() method is invoked, that is, the promise is resolved. If the request fails, then the callback is passed to the catch() method, which is invoked, and the promise is rejected. The then() method always returns a promise, making it possible to run multiple asynchronous operations one after another. In the code, you can see how asynchronous operations are chained. What’s important here is that the then() methods are invoked only once, that is, the promise returned by the $http() method can be resolved only once, and multiple attempts to resolve a promise will be ignored. Therefore, we cannot use promise patterns to write reactive code when we have to deal with multiple activities or data updates of an asynchronous operation.
Due to the problems with the event-driven, callback, and promise patterns, there was a need for another pattern, and functional reactive programming came to the rescue.
FRP is simply reactive programming using functional programming style. We will learn more about functional programming in the next section. Actually, the drawbacks of the event-driven, callback, and promise patterns weren’t the real reason for the invention of FRP; rather, FRP was actually invented because there were demands for a functional pattern for reactive programming, as functional code is easy to write, test, debug, reuse, update, and read. But as FRP solves the problems caused by the event-driven, callback, and promise patterns, we can say that FRP is an alternative to the other patterns.
In this article, we will learn about FRP, which is considered the modern way of writing reactive code.
Functional programming in a nutshell
Before we get into FRP, it’s necessary to have basic knowledge about functional programming.
In a nutshell, functional programming is a style of writing code in which we use only pure function calls (including recursion) instead of loops and conditionals, and data is immutable.
So what is a pure function? A pure function is a function that depends only on its input arguments and that always provides the same output for a particular input. If it reads anything else outside of its scope, including global variables, then it’s not a pure function.
Obviously, it’s not always possible to make all the functions pure. For example, a function that fetches a web page or reads from the filesystem cannot guarantee the same return value. We should try to make as many as functions as pure as possible.
So, we can say that 100% purity is impossible to achieve, but 85% purity is still very productive.
As data is immutable in functional programming, you must be wondering how it is possible to write code without modifying data. Well, in practice, we simply create new data structures instead of modifying existing ones. For example, if we have an array with four values and we want to remove the last one, then we simply create a new array, which doesn’t have the last value.
The advantages of immutable data
There are several advantages of immutable data. Here are a few of them:
- They are thread-safe, that is, multiple threads operating on them cannot modify/corrupt their state. Learn more about thread safety at https://en.wikipedia.org/wiki/Thread_safety.
- They object copying can be shared easily. One doesn’t have to employ a strategy such as defensive copying, like in mutable data structures. Learn more about object copying at https://en.wikipedia.org/wiki/Object_ copying.
- They help avoid temporal coupling. More about temporal coupling can be found at https://en.wikipedia.org/wiki/Coupling_(computer_ programming)#Object-oriented_programming.
Functional data structures
As data is immutable, there are several problems you are likely to face. Here are a few:
- If an immutable array has millions of values, then creating a new array and copying all the values from the previous array is CPU and memory intensive
- If two threads need to write to the same variable, coordinating the final value of the variable will be difficult
There are many other issues. These issues led to the idea of functional data structures. Functional data structures are a different type of data structure that aim to solve these kinds of issue. But you don’t need to know about functional data structures to follow this article or write functional reactive code in JavaScript.
The advantages of pure functions
Here are a few advantages of pure functions:
- They increase reusability and maintainability, as each function is independent
- Easier testing and debugging is possible, as each function can be tested and debugged separately
- Functional programs are easy to understand as they are written in a declarative manner, that is, the code says what is to be done instead of how it’s done.
Functional programming with JavaScript
You don’t have to use a functional programming language such as Erlang, Haskell, and so on to write functional code. Most imperative programming languages allow us to write functional code.
Due to the fact that functions in JavaScript are first-class (we will learn more about first-class functions later), it is possible to write functional code in JavaScript.
A function is said to be first-class when it can be passed as an argument to another
function, can return another function, and be assigned to a variable.
In JavaScript, functions are first-class because they are objects. Because an object can be passed as an argument to another function, a function can return an object, and an object can be assigned to a variable, functions can be first-class.
Functional programming helper functions
Functional programming languages provide a lot of in-built functions called helper functions to make it easy to write functional code. For example, as we cannot use loops for iteration in functional code, we need some sort of function to take a collection and map each value of the collection to a function. Functional programming languages provide the map helper function for this purpose. Similarly, there are a lot of other helper functions for different purposes.
As JavaScript is not a functional programming language, it doesn’t come with functional helper functions. However, ES6 introduced some helper functions, such as Array.from(), Array.prototype.from(), and Array.prototype.find(). Still, this list is not enough to write functional code. Therefore, developers use libraries such as Underscore.js to write functional code.
Getting started with FRP
FRP is simply reactive programming using functional programming style.
EventStreams and properties (don’t get these confused with object properties) are the building blocks of FRP. Let’s look at an overview of what both these terms mean.
EventStreams
An EventStream represents a stream of events. Events in an EventStream may happen at any time and need not occur synchronously.
Let’s understand EventStreams by comparing them to events in an event-driven pattern. Just like we subscribe to events in an event-driven pattern, we subscribe to EventStreams in FRP. Unlike events in event-driven programming, the power of
EventStreams is that they can be merged, concatenated, combined, zipped, filtered,
or transformed in any number of ways before you handle and act on the events.
In functional programming, data is immutable, so merging, concatenating, combining, zipping, filtering, or transforming an EventStream creates a new EventStream instead of modifying the existing one.
Here is a diagram that shows how an EventStream representing the click event of a UI element would look:
This EventStream can be merged with any other stream. Here is a diagram that shows how it looks when two EventStreams are merged:
Merging can be useful when we want to apply the same action when an event occurs to two different EventStreams. Instead of subscribing and attaching a callback to two different EventStreams, we can now subscribe to a single EventStream, eliminating duplicate code and making it easy to update code. Merging can be useful in various other cases as well.
Properties
A property represents a value that changes over time. Properties can be used as an alternative to JavaScript variables whose values change in response to asynchronous activities and data updates. For example, you can use properties to represent the total number of times a button was clicked, the total number of logged-in users, and so on.
The advantage of using properties instead of JavaScript variables is that you can subscribe to properties, that is, whenever the value of a property changes, a callback is fired to update the parts of the system that depend on it. This prevents code duplication and has many other benefits.
We’ve just looked at the basics of FRP. Creating EventStreams and properties, their methods, and other things to work with them differ depending on the library we use to write functional reactive code. Now, let’s explore how to write functional reactive code using the Bacon.js library.
FRP using Bacon.js
Bacon.js is a JavaScript library that helps us write functional reactive code in JavaScript. It can be used for both frontend and backend JavaScript. The official website of Bacon.js library is https://baconjs.github.io/.
Let’s create a basic website project to demonstrate FRP with Bacon.js.
Setting up the project
Let’s learn how to download and install Bacon.js for use with frontend and backend JavaScript. On the frontend, Bacon.js depends on jQuery.
Create a directory named baconjs-example. Inside it, create files called package. json and app.js and a directory called public. Inside the public directory, create directories called html and js. Inside the html directory, create a file called index. html. Finally, inside the js directory, create a file called index.js.
Download the frontend Bacon.js library from http://cdnjs.cloudflare.com/ ajax/libs/bacon.js/0.7.73/Bacon.js and jQuery from https://code.jquery. com/jquery-2.2.0.min.js, and place them in the js directory.
At the time of writing this article, 0.7.73 was the latest version of the frontend Bacon.js library, and 2.2.0 was the latest version of jQuery.
In the index.html file, place this code to queue jQuery and the frontend Bacon.js library:
<!doctype html>
<html>
<head>
<title>Bacon.js Example</title>
</head>
<body>
<script type=”text/javascript” src=”js/jquery-2.2.0.min.js”></ script>
<script type=”text/javascript” src=”js/Bacon.js”></script>
<script type=”text/javascript” src=”js/index.js”></script>
</body>
</html>
In the package.json file, place this code:
{
“name”: “Baconjs-Example”, “dependencies”: {
“express”: “4.13.3”,
“baconjs”: “0.7.83”
}
}
Now, run npm install inside the baconjs-example directory to download the npm packages.
At the time of writing this article, 0.7.83 was the latest version of backend Bacon.js library.
In the app.js file, place the following code to import the backend Bacon.js and Express modules. It also starts our webserver in order to serve the web page and static files:
var Bacon = require(“baconjs”).Bacon; var express = require(“express”);
var app = express();
app.use(express.static( dirname + “/public”)); app.get(“/”, function(httpRequest, httpResponse, next){
httpResponse.sendFile( dirname + “/public/html/index.html”);
})
app.listen(8080);
We have now set up a basic Bacon.js project. Run node app.js to start the web server. Now, let’s explore Bacon.js APIs.
Bacon.js APIs
Bacon.js provides APIs to do almost anything that’s possible using EventStreams and properties. The method of importing and downloading Bacon.js for the backend and frontend is different, but the APIs are the same for both. Let’s look at the most important APIs provided by Bacon.js.
Creating EventStreams
There are various ways of creating EventStreams, depending on how an asynchronous API is designed, that is, which pattern an asynchronous API follows. An asynchronous API follows the event-driven, promise, or callback pattern.
We need to wrap these patterns with Bacon-provided APIs to connect their data updates or activity updates to event streams, that is, convert them to functional reactive patterns.
If we want to create an EventStream for a UI element on a web page, we can use the
$.asEventStream() method. Let’s look at an example of how it works. Place the following code in the <body> tag of the index.html file to create a button:
<button id=”myButton”>Click me!!!</button>
In an event-driven pattern, to print something whenever a button is clicked, we would write something like this:
document.getElementById(“myButton”).addEventListener(“click”, function(){
console.log(“Button Clicked”);
}, false)
But in Bacon.js, we will write it this way. Place this code in the index.js file:
var myButton_click_stream = $(“#myButton”).asEventStream(“click”); myButton_click_stream.onValue(function(e){
console.log(“Button Clicked”);
})
Here, we use a jQuery selector to point to the button, and we then use the
$.asEventStream method to connect its click events to an EventStream. The
$.asEventStream method takes the name of the event as its first parameter.
The onValue method is used to add subscribers to an EventStream. The onValue method of an EventStream takes a callback, which is executed every time a new event is added to the EventStream. The callback has a single parameter, which represents the current event that has been added to the EventStream. In this case, it’s of the event interface. We can call the onValue method multiple times to add multiple callbacks.
A subscriber can be used to update the UI, perform logging, and so on. But the logic code for handling the event should be written using the helper functions and not be inside the subscriber. This is how functional reactive code is supposed to be written.
The subscriber callback will not be invoked for events that occurred before the subscriber was registered.
Similarly, there are lots of other APIs provided by Bacon.js to create EventStreams. Here are a few of them:
- Bacon.fromPromise: This is used to create an EventStream from a promise object.
- Bacon.fromEvent: This is used to create an EventStream from events of an EventTarget or Node.js EventEmitter object.
- Bacon.fromCallback: This is used to create an EventStream from a function that accepts a callback.
- Bacon.fromNodeCallback: This is the same as Bacon.fromCallback, but it requires the callback to be called in Node.js convention.
- Bacon.fromBinder: If none of the previous APIs are fitting well, then you can use this one.
Creating properties
A property is created from an EventStream, that is, a stream whose events the value of the property depends on. Whenever an event occurs in the EventStream, a callback is executed to update the property value.
You can create a property for an EventStream using either the toProperty or scan methods. The scan method is used instead of toProperty when we want to give an initial value as well as an accumulator function to the property. You may or may not provide an initial value when creating a property using toProperty().
Calling scan or toProperty multiple times create multiple properties.
Let’s create a property to hold the total number of times a button is clicked. Here is the code to do this; place it in the index.js file:
var button_click_counter = myButton_click_stream.scan(0, function(value, e){
return ++value;
})
button_click_counter.onValue(function(value){
console.log(“Button is clicked ” + value + ” number of times”);
})
Here, we created a property using the scan method and initialized it to 0. The second argument is a callback, which is invoked to update the property value whenever an event happens in the EventStream to which the property is attached. This callback should return the new property value. The callback has two parameters, that is, the current value of the property and the event.
The onValue method of a property takes a callback that is executed every time the property value changes. We can call the onValue method multiple times to register multiple callbacks.
When we register a subscriber for a property, the subscriber is executed with the current value as soon as it’s registered, but not for the values that occurred before it had been registered. If the property has not yet been assigned to anything, then the callback is not executed.
Here, whenever the property value changes, we log a statement informing us about the total number of times the button was clicked.
A property can also be created from another property. This is useful when a property’s value depends on another property. Let’s create a property from the previous property, which holds the time at which the property was last clicked and the button click count at that time. Here is the code to do this; place it in the index.js file:
var button_click_time = button_click_counter.scan({}, function(value, count){
return {time: Date.now(), clicks: count};
})
button_click_time.onValue(function(value){ console.log(value);
})
Everything here is self-explanatory. The only thing you need to know is that the second parameter of the second argument passed to the scan method represents the value of the property we used to create this property.
A property holds a stream that has all of its previous and current values internally; therefore, we can also merge, combine, zip, sample, filter, and transform properties. Merging, combining, zipping, sampling, filtering, or transforming properties gives us new properties. This feature is useful for writing code for the more complex situation of a property’s value depending on another property. For example, if we want to ignore some values of a property while calculating the value of another property based on it, then we can use filter feature.
Bacon.js also allows us to create EventStreams based on properties, that is, the events of an EventStream represent the values of a property. Events in these EventStreams occur when their respective property value is changed. This feature has many benefits, one of which is that it can prevent code duplication when we have to trigger the same action in response to several properties changing their values.
To create EventStreams based on properties, we can use the toEventStream method of a property.
Merging, filtering, and transforming EventStreams and properties
Bacon.js provides various helper functions to work with EventStreams and properties. Let’s look at some of the most useful helper functions.
Merging
Merging streams or properties gives us a new stream or property that delivers all the events or values of all the streams or properties. To merge EventStreams or properties, we can use their Bacon.mergeAll method instances. Here is some example code to demonstrate this. Place it in the index.js file:
var merged_property = Bacon.mergeAll([button_click_counter, button_ click_time]);
merged_property.onValue(function(e){ console.log(e);
})
Here, we merge two properties. Bacon.mergeAll takes an array of either EventStreams or properties. Whenever the value of either of the two properties changes, the value is made the current value of the resultant property.
There are various other helper functions available for merging properties and EventStreams.
Filtering
Filtering is removing specific events or values from EventStreams or properties,
respectively, that we don’t need.
Bacon.js provides a lot of helper functions to filter EventStreams and properties, depending on what you want to filter. Let’s look at the filter method for EventStreams and properties that let us filter based on a predicate function; that is, if the function returns true, then the value is accepted; otherwise, it is rejected.
Let’s look at example code to demonstrate this. In the index.js file, find this code:
var myButton_click_stream = $(“#myButton”).asEventStream(“click”); myButton_click_stream.onValue(function(e){console.log(e); console.log(“Button Clicked”);
})
Replace that with this code:
var myButton_click_stream = $(“#myButton”).asEventStream(“click”). filter(function(e){
return e.shiftKey === true;
});
myButton_click_stream.onValue(function(e){ console.log(e);
console.log(“Button Clicked”);
})
Here, we are filtering all those click events in which we didn’t press the Shift key. So, for the click event to be accepted, we need to press the Shift key while clicking on the button.
You can think of filtering as an alternative to using the if…else conditional.
Transforming
Transforming is creating an EventStream or property from another EventStream or property, respectively, whose events are transformed to something else. For example, a property whose value represents a URL can be transformed to another property, whose value represents the response of the URL. Transforming EventStreams and properties actually creates new EventStreams and properties, respectively.
You can think of transforming as an alternative to loops, that is, to using for loops.
There are several helper functions provided by Bacon.js for transformation depending on how and what you want to transform.
One popular transformation function is map(), which maps events or values of EventStreams or properties to a function. Let’s look at a code sample to demonstrate this. Find this code in the index.js file:
var button_click_time = button_click_counter.scan({}, function(value, count){
return {time: Date.now(), clicks: count};
})
Replace it with this code:
var button_click_time = button_click_counter.scan({}, function(value, count){
return {time: Date.now(), clicks: count};
}).map(function(value){
var date = new Date(value.time);
return (date).getHours() + “:” + (date).getMinutes();
})
Here, we are using map() to transform the Unix timestamp to the HH:MM format, which is easy to understand.
There is another, vital transformation helper function provided by Bacon.js called
flatMap. There are basically two differences between flatMap and map:
- The flatMap function always returns an EventStream regardless of whether it was called on a EventStream or property.
- If the callback passed to flatMap returns an EventStream or property, then the events of the EventStream returned by the flatMap function are events and values of the streams and properties returned by the callback passed to flatMap. Whenever an event or value is added to the streams and properties returned by the callback passed to flatMap, the event and value will automatically be added to the EventStream returned by the
flatMap function.
You need to use flatMap instead of map when retrieving the return value of a callback passed to a network, disk drive, or somewhere else asynchronous. For example, in the previous example, where I talked about transforming a URL to a URL response, we need to use flatMap instead of map as instead of a callback, we need to make an AJAX request, and its response will be captured as a stream, and the stream will be returned. When the AJAX request completes, the event will be put inside the stream returned by the flatMap function.
Let’s look at an implementation of this example. First, create an input text field and
place it in the index.html file, as follows:
<input id=”url” type=”url”>
Now, let’s write code using Bacon.js to log the output of the URL entered in the field when a user hits the Enter key. Here is the code to do this. Place it in the index.js file:
var enter_key_click_stream = $(“#url”).asEventStream(“keyup”). filter(function(e){
return e.keyCode == 13;
})
var url = enter_key_click_stream.scan(“”, function(value, e){ return e.currentTarget.value;
})
var response = url.flatMap(function(value){ return Bacon.fromPromise($.ajax({url:value}));
}).toProperty();
response.onValue(function(value){ console.log(value);
})
This is how the code works:
- First, we create an EventStream for the keyup event.
- Then, we filter only Enter-key events because we will take action only if the
Enter key is pressed.
- Then, we create a variable to hold the value of the text field.
- Then, we use flatMap to fetch the response of the URL using jQuery AJAX. We are using Bacon.fromPromise to create an EventStream from a promise.
- When the AJAX request finishes, it adds the response to the EventStream returned by the callback passed to flatMap. Then, flatMap adds the same response to the EventStream returned by the flatMap function itself. As soon as it’s added, we log the response using onValue.
Here, if we had used map instead of flatMap, then we would have ended up logging EventStream objects instead of the events of the EventStream returned by the map function.
Although we can have both url and response properties directly created from the enter_key_click_stream, it is likely to cause code repetition and make the code difficult to understand.
Summary
We looked at reactive programming, functional programming, FRP, and finally an overview of Bacon.js. You should now be comfortable with writing basic functional reactive code and have a clear idea of its benefits.
We will learn about more of the APIs provided by Bacon.js and build a real-world project using Bacon.js in the next article.
Here are related articles if you wish to learn more advance topics for web development:
Best practices for securing and scaling Node.JS applications
Comprehensive overview of Angular 2 architecture and features
How Bootstrap 4 extensible content containers or Cards work
Comprehensive guide for migration from monolithic to microservices architecture
Comprehensive overview of Bootstrap 4 features for user interface customizations
Using advance js and webrtc for cross browser communications in real time
Intro to real-time bidirectional communications between browsers and webSocket servers
Junior or senior web developers can also explore career opportunities around blockchain development by reading below articles:
Blockchain Developer Guide- Comprehensive Blockchain Ethereum Developer Guide from Beginner to Advance Level
Blockchain Developer Guide- Comprehensive Blockchain Hyperledger Developer Guide from Beginner to Advance Level