Develop advance JS applications with functional reactive programming


Develop advance JS applications with functional reactive programming

Recipe ID: hsts-r51


Recipe Overview


In our previous article, we reviewed and discussed Functional Reactive Programming or FRP concepts and topics. The best way to master FRP using Bacon.js is by building a real world application, which is what we will do in this recipe. We will build an advanced profile search widget, just like the ones you would usually find on social networking or dating sites. To keep the recipe short and to the point, we will work with some sample data instead of building registration functionality. We will also learn some more advanced concepts of functional programming and Bacon.js.
In this recipe, we will cover the following:

Errors in Bacon.js

Bacon provides the Bacon.Error constructor to explicitly mark events or values of EventStreams or properties respectively as errors so that Bacon can identify them and open up a wide variety of other APIs to work with those errors specifically.

Depending on how we create a stream, Bacon.js can sometimes identify whether an event is a success or error event, and if it's an error event, then it can convert it to Bacon.Error. For example, if we use Bacon.fromPromise to create an EventStream, then Bacon can identify an error easily, since when an error occurs in a promise pattern, the second callback of the then() method or the callback passed to the catch() method is executed.
In case Bacon cannot identify whether an event is an error or success event while creating a stream, then we need to explicitly create instances of Bacon.Error and replace the error events with them. For example, when using Bacon.fromCallback, there is no way for Bacon.js to know whether an event is a success or error event, so we need to explicitly convert error events to instances of Bacon.Error.

Subscribing to errors

A callback passed to onValue is not invoked for Bacon.Error events or values; instead, we need to use onError.
To see it in action, open the index.js file that we created in our previous recipe,
and add this code:
response.onError(function(error){
console.log("An error occured while fetching the page", error);
})
Now, if you enter an URL that cannot be fetched, a custom error message is displayed on the console.

Mapping errors

The map() function doesn't map Bacon.Error instances; therefore, Bacon provides us with mapError (), which works the same way as map but maps only Bacon.
Error instances.
Similarly, flapMap() doesn't map Bacon.Error instances. Therefore, Bacon provides us with flatMapError(), which works the same way as flatMap but maps only Bacon.Error instances. Aside from flatMap and map, Bacon.Error instances can pass through everything.

Retrying a function call

Sometimes, we might want to retry an operation if it fails. For example, if we fail to retrieve a web page using AJAX due to a server timeout error, then we might want to try retrieving it again after some time.
Bacon provides the Bacon.retry function, using which we can make a function call again and again as long as we want to.
Bacon.retry returns an EventStream, and it takes an object with four properties, as follows:

The EventStream returned by Bacon.retry has the event or value that was present in the last EventStream or property returned by the last call to the source function.
Let's see the Bacon.retry function in action. Find this code in the index.js file:
var response = url.flatMap(function(value){ return Bacon.fromPromise($.ajax({url:value}));
}).toProperty();
Replace it with this code:
var response = url.flatMap(function(value){ return Bacon.retry({
source: function(){ return Bacon.fromPromise($.ajax({url:value})); }, retries: 5,
isRetryable: function (error) { return error.status !== 404;
},
delay: function(context) { return 2000; }
})
}).toProperty();
Here, we are retrying the AJAX request 5 times after every 2 seconds for any error other than 404.

Ending an EventStream or property on error

An EventStream or property is said to have ended when you cannot push anything to it.
If you want to end an EventStream or property when a Bacon.Error instance is pushed, then you need to call the endOnError method of the EventStream or property. The endOnError method returns a new EventStream or property, which is ended when a Bacon.Error instance is pushed.

Handling exceptions

If an exception is encountered inside a callback passed to a Bacon helper function, then it's not caught automatically; rather, we have to use a try…catch statement to handle it. A common practice is to return a Bacon.Error instance after catching an exception so that we can handle it just like an error.
Here is an example of how to handle exceptions. In the index.js file, find the following code:
var response = url.flatMap(function(value){ return Bacon.retry({
source: function(){ return Bacon.fromPromise($.ajax({url:value})); }, retries: 5,
isRetryable: function (error) { return error.status !== 404;
},
delay: function(context) { return 2000; }
})
}).toProperty();
Replace it with this:
var response = url.flatMap(function(value){ try
{
return Bacon.retry({
source: function(){ return Bacon.fromPromise($.ajax({url:value})); },

retries: 5,
isRetryable: function (error) { return error.status !== 404;
},
delay: function(context) { return 2000; }
})
}
catch(e)
{
return new Bacon.Error(e);
}

}).toProperty();
Here, we are catching exceptions and creating a new Bacon.Error instance with the exception as the details of the error, that is, we are passing the exception as an argument to the constructor.

Constant properties

Bacon also provides us ways to create constant properties. Constant properties are initialized at the time of creation and cannot be reinitialized, that is, new values cannot be pushed.
A constant property is created using the Bacon.constant() constructer. We need to pass the value of the property to the constructor. A constant property can be merged, concatenated, combined, zipped, sampled, filtered, and transformed.
Here is an example of how to create a constant property. Place this code in the
index.js file:
var script_start_time = Bacon.constant(Date.now()).map(function(value){
var date = new Date(value);
return (date).getHours() + ":" + (date).getMinutes() + ":" + (date).getSeconds();
});

script_start_time.onValue(function(value){ console.log("This script started running at : " + value);
})
Here, the constant property stores the time at which the script was started and prints the time.

 

An overview of buses

A bus is just like an EventStream, but it lets us push values into the stream manually instead of attaching it to a source, and it also allows plugging other EventStreams and properties into the bus on the fly.
Here is an example that demonstrates how to create a bus and various methods provided by a Bacon.Bus instance. Place this code in the index.js file:
var bus1 = new Bacon.Bus();

bus1.onValue(function(event){ console.log(event);
})

bus1.push(1);
bus1.push(2);
var bus2 = new Bacon.Bus(); bus1.plug(bus2); bus2.push(3);
bus1.error("Unknown Error"); //pushed an Bacon.Error bus1.end();
bus2.push(4); //this will not be pushed as bus has ended
The code is self explanatory. The output of the above code is as follows:
1
2
3

 

Subscribing to the end of EventStreams and properties

Bacon provides the onEnd method to subscribe to callbacks that will be executed when an EventStream or property ends.
Here is some example code, which shows you how to use the onEnd callback. Place it in the index.js file:
script_start_time.onEnd(function(){
console.log("Script start time has been successfully calculated and logged");
})
Here, we are attaching an onEnd callback to the constant property, which we created previously. After initialization, the property is ended; therefore, the onEnd callback is invoked. We can register multiple subscribers as well.
Actually, to end an EventStream or property, Bacon internally pushes an instance of the Bacon.End constructor. So, we can also use the Bacon.End constructor to end an EventStream or property.
Let's look at an example of how to use Bacon.End. Place this code in the index.js file:
var custom_stream = Bacon.fromBinder(function(sink) { sink(10);
sink(20);
sink(new Bacon.End()); //event stream ends here sink(30); //this will not be pushed
});

custom_stream.onValue(function(event){ console.log(event);
});
The output of the code is this:
10
20
A Bacon.End instance doesn't pass through helper functions.

Unplugging subscribers

We saw how to subscribe to an EventStream and property using onValue, onError, and onEnd. We can also unsubscribe the subscribers if we don't need them anymore.
These functions return a function for unsubscribing. To unsubscribe, we need to call the function returned by the subscriber function.

Combining and zipping

Bacon provides certain methods to combine and zip properties and EventStreams.
There is a significant difference between combining and zipping.

 

When we combine properties, we always get a property, which will have an array of all source properties as its value. If we try to combine EventStreams, then they are first converted to properties before combining takes place. When there is a push in any one of the source properties, a new value is pushed into the resultant property. Combining starts after each of the source properties has a value pushed.
Here is an example to demonstrate combining. Place this code in the index.js file.
var x1 = new Bacon.Bus(); var x2 = new Bacon.Bus(); var x3 = new Bacon.Bus();

Bacon.combineAsArray(x1, x2, x3).onValue(function(value){ console.log(value);
})

x1.push(0);
x1.push(1);
x2.push(2);
x3.push(3);
x3.push(4);
x1.push(5);
Here is the output of the code:

[1,

2,

3]

[1,

2,

4]

[5,

2,

4]

Zipping is different from combining. Zipping means that events from each source are combined pairwise so that the first event from each source is published first, then the second event, and so on. The results will be published as soon as there is a value from each source. When we zip properties and EventStreams, we always get an EventStream.
Here is an example to demonstrate zipping. Place this code in the index.js file:
var y1 = new Bacon.Bus(); var y2 = new Bacon.Bus(); var y3 = new Bacon.Bus();

Bacon.zipAsArray(y1, y2, y3).onValue(function(value){ console.log(value);
})

y1.push(0);

y1.push(1);
y2.push(2);
y3.push(3);
y3.push(4);
x1.push(5);
Here is the output of the code:
[0, 2, 3]

 

Lazy evaluation

In programming, lazy evaluation is a strategy that delays the evaluation of values until they're needed. There are two means by which lazy evaluation is implemented by Bacon.js.

Type 1

A stream or property will not be attached to its data source until it has subscribers. Let's look at an example to understand this. Place this code in the index.js file:
var myButton_click_stream1 =
$("#myButton").asEventStream("click").map(function(event){ console.log(event);
return event;
});
Here, when you click on the myButton button, nothing will be logged. Now, place this code in the index.js file:
myButton_click_stream1.onValue(function(event){}) Now when you click on the button, the event will be logged. The log method is also considered as a subscriber.

Type 2

Methods such as map and combine* use lazy evaluation to avoid evaluating events and values that aren't actually needed. Lazy evaluation results in huge performance benefits in some cases.
But how do map and combine* know whether an event or value is not needed? Well, there are a few methods that give a hint about this to map and combine*, for example, sampledBy.

 

Let's look at an example of how map implements lazy evaluation. Place this code in the index.js file:
var myBus_1 = Bacon.Bus(); var myBus_2 = Bacon.Bus();

var myProperty_1 = myBus_1.map(function(event){ console.log(""Executing 1"");
return event;
}).toProperty();
var myStream_1 = myProperty_1.sampledBy(myBus_2); myStream_1.onValue(function(event){
console.log(""Logged"", event);
})

myBus_1.push(1);
Here is what we are doing in the previous code:

The previous code looks like it should log the following output:
Executing 1
Logged 1
Unfortunately, it doesn't log anything. That's because lazy evaluation is taking place here. The sampledBy function takes the current value of the property, not the ones that were generated from previous events. Therefore, map decides to generate the property value when an event occurs in the second bus, therefore preventing unnecessary calls to the callback passed to the map function. In short, here, map simply prevents calculating property values until it's actually needed. Now, add this code to the index.js file:
myBus_1.push(2); myBus_2.push();
Now, when you run the code, you will get this output:
Executing 1
Logged 2
Here, you can see that map prevented calculating for the first event pushed inside the first bus. It calculated the property value for second event because sampling was done after that.

 

Building the profile search widget

We've covered almost all the important APIs and concepts of Bacon.js. Now, it's time to build the profile search widget. We will also learn some more APIs and concepts  in the process.
We will build the profile search widget to learn how to write reactive code using Bacon for both the frontend and backend in real-world projects. Let's get started.

 

Understanding project directories and files

In the exercise files of this recipe, you will find a directory named
profile-search-widget. Inside that directory, you will find two other directories named final and initial. The final directory contains the final code for the profile search widget whereas the initial directory contains the files and code for you to quickly get started with building the profile search widget. You will now work with the initial directory.
You are supposed to put the server-side code inside the app.js file and the frontend code inside the public/js/index.js file. Currently, the app.js file imports Bacon, Express, and filesystem modules and also has basic code to run the web server and serve static files.
Inside the public/html/index.html file, you will find HTML code. We will not be
writing any HTML or CSS.
Let's first build the backend and then the frontend.

Converting Express.js routes to a functional reactive pattern

Express.js routes are written using a callback pattern. We need a wrapper to convert the callback pattern to a functional reactive pattern.
Bacon doesn't provide any direct method for doing this—there are various other custom methods. The easiest and shortest way to do this is by creating a bus for every route, and whenever a request to a route is made, pushing an event into its respective bus. Let's create a route this way for serving the index.html file for requests to the root URL. Place this code in the app.js file:
function route_eventstream(path)
{
var bus = new Bacon.Bus();

app.get(path, function(req, res) { bus.push({
req: req, res: res
});
});

return bus;
}
var root_stream = route_eventstream("/"); root_stream.onValue(function(event){
event.res.sendFile( dirname + ""/public/html/index.html"");
})
This is how the code works:

an event is pushed into the root EventStream.
Now, run the node app.js command and visit localhost:8080 in your browser. This is the output you will see:
Web design with Functional Reactive Programming

Making the user experience better

In the previous screenshot, you can see that there are eight fields based on which a user can perform a search.

Instead of a user just filling some of the fields and clicking on the Search button to get the result, we can add some more features to make the user experience better. Here are the extra things we are going to add:

These features will make the frontend code more complex, which will give us a chance to explore how to write complex logic using Bacon.

The company suggestions route

Let's create a route that responds with an array of company name suggestions based on a given value. Later on, to populate the company name text field drop-down menu, we will make a request to this route.
We will not build functionality to add profiles; instead, we will simply retrieve profiles from a JSON file that has some random profiles. In the initial directory, you will find a file named data.json, which has some profiles in it.
Let's first read the data from the data.json file. Here is the code for this. Place it in
the app.js file.
var data = Bacon.fromNodeCallback(fs.readFile, "data.json", "utf8").map(function(event){
return JSON.parse(event);
}).toProperty();
Here, we are reading the data in functional reactive style and then converting the EventStream to a property, which represents the data.
Here is the code for the company suggestion route. Place it in the app.js file:
function findMatchingCompanyName(list, companyName)
{
return list.filter(function(value){ return companyName != "" &&
value.company.toLowerCase().indexOf(companyName.toLowerCase()) ==
0;
})
}

var company_dropdown_list_stream = route_eventstream(""/company/dropdown"");

var company_dropdown_list_data_stream = Bacon.combineAsArray([data, company_dropdown_list_stream]).map(function(event){
return findMatchingCompanyName(event[0], event[1].req.query.companyName);
}).toEventStream();

Bacon.zipAsArray(company_dropdown_list_stream, company_dropdown_list_ data_stream).onValues(function(event1, event2) {
event1.res.send(event2);
});
Here is how the code works:

dropdown_list_data_stream so that we get the reference to the connection object as well as the final result. We then attach a subscriber to the zipped EventStream, which sends the response.

 

The search result route

Let's create a route that responds with an array of profiles based on a given parameter. This will be used to find the search result. Later on, from the frontend, we will make a request to this route.
Here is the code for this route. Place it in the app.js file:
function findMatchingProfilesForEmail(list, email)
{
return list.filter(function(value){ return value.email == email;
})
}

function findMatchingProfiles(list, firstName, lastName, gender, skill, company, dob, address)
{
var firstName_matches = list.filter(function(value){
return firstName == "" || value.first_name.toLowerCase() == firstName.toLowerCase();
})

var lastName_matches = firstName_matches.filter(function(value){ return lastName == "" || value.last_name.toLowerCase() == lastName.toLowerCase();
})

var gender_matches = lastName_matches.filter(function(value){ return gender == "" || value.gender.toLowerCase() == gender.toLowerCase();
})

var skill_matches = gender_matches.filter(function(value){ return skill == "" || value.skill.toLowerCase() == skill.toLowerCase();
})

var company_matches = skill_matches.filter(function(value){ return company == "" || value.company.toLowerCase() == company.toLowerCase();
})
var dob_matches = company_matches.filter(function(value){ return dob == "" || value.dob == dob;
})

var address_matches = dob_matches.filter(function(value){ return address == "" || value.address.toLowerCase() == address.toLowerCase();
})

return address_matches;
}

var profile_search_stream = route_eventstream("/search");

var profile_search_data_stream_for_email = Bacon.combineAsArray([data, profile_search_stream.filter(function(event){
return event.req.query.email != "";
})]).map(function(event){
return findMatchingProfilesForEmail(event[0], event[1].req.query.email);
}).toEventStream();

var profile_search_data_stream_for_others = Bacon.combineAsArray([data, profile_search_stream.filter(function(event){
return event.req.query.email == "";
})]).map(function(event){
return findMatchingProfiles(event[0], event[1].req.query.firstName, event[1].req.query.lastName, event[1].req.query.gender, event[1].req.query.skill, event[1].req.query.company, event[1].req.query.dob, event[1].req.query.address);
}).toEventStream();

Bacon.zipAsArray(profile_search_stream, Bacon.mergeAll([profile_search_data_stream_for_email, profile_search_data_stream_for_others])).onValues(function(event1, event2) {
event1.res.send(event2);
});

This is how the code works:

Building the frontend

We are done building the backend part of our profile search widget. Now, we need
to write the frontend part.
Before we get into it, it's worth looking at the code in the index.html file:
<!doctype html>
<html>
<head>
<title>Advanced Profile Search Widget</title>

<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<div class="container">
<div class="section-1">
<h3>Provide search information</h3>
<hr>
<div class="form-style">
<form action="" method="post">
<label><span>First Name</span><input type="text" class="input-field" id="first-name" value=""
/></label>
<label><span>Last Name </span><input type="text"

class="input-field" id="last-name" value=""
/></label>
<label>
<span>Email</span>
<input type="email" class="input-field" id="email" value="" />
<br><small class="hide" id="email-error">Email address is invalid</small>
</label>
<label>
<span>Gender</span>
<select id="gender" class="select-field">
<option value="male">Male</option>
<option value="female">Female</option>
</select>
</label>
<label><span>Company</span><input list="companies" type="text" class="input-field" value="" id="company" /></label>
<label><span>Address</span><input type="address" class="input-field" value="" id="address"
/></label>
<label><span>Skill</span><input type="text" class="input-field" value="" id="skill" /></label>
<label><span>DOB</span><input placeholder="mm/dd/yyyy" type="text" class="input- field" value="" id="dob" /></label>
<label><span>&nbsp;</span><input type="button" value="Search" id="search" /></label>

<datalist id="companies"></datalist>
</form>
</div>
</div>
<div class="section-2">
<h3>Search Result</h3>
<hr>
<ul id="search-result">
</ul>
</div>
<div class="clear"></div>
</div>

<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>
Most of the code is self-explanatory. Here are a few things you need to pay special attention to:

hide, which hides it. Removing the class will unhide it.

Now, let's create EventStreams for keyup events on the input fields and store the current value of the fields in properties. Here is the code for this. Place it in the index.js file:
var first_name_keypress_stream = $("#first- name").asEventStream("keyup");

var first_name = first_name_keypress_stream.scan("", function(value){
return $("#first-name").val();
});

var last_name_keypress_stream = $("#last- name").asEventStream("keyup");

var last_name = last_name_keypress_stream.scan("", function(value){
return $("#last-name").val();
});

var email_keypress_stream = $("#email").asEventStream("keyup");

var is_email_valid = email_keypress_stream.scan("", function(value){
return $("#email").val();
}).map(function(value){
var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()
[\]\\.,;:\s@"]+)*)|(".+"))@((\[[
0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-
9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(value);

});

var email = Bacon.mergeAll(is_email_valid.filter(function(value){ return value == true;
}).map(function(value){
$("#email-error").addClass("hide");
return $("#email").val();
}), is_email_valid.filter(function(value){ return value == false;
}).map(function(value){
$("#email-error").removeClass("hide"); return "";
}))
var gender_select_stream = $("#gender").asEventStream("change"); var gender = gender_select_stream.scan("male", function(value){
return $("#gender option:selected").val()
})

var company_keypress_stream  =
$("#company").asEventStream("keyup");

var company = company_keypress_stream.scan("", function(value){ return $("#company").val();
});

var address_keypress_stream  =
$("#address").asEventStream("keyup");

var address = address_keypress_stream.scan("", function(value){ return $("#address").val();
});
var skill_keypress_stream    = $("#skill").asEventStream("keyup"); var skill = skill_keypress_stream.scan("", function(value){
return $("#skill").val();
});
var dob_keypress_stream = $("#dob").asEventStream("keyup"); var dob = dob_keypress_stream.scan("", function(value){
return $("#dob").val();
});
Most of the above code is self-explanatory. The only thing that you need to understand is that instead of directly assign the e-mail field value to the e-mail property, we first validate whether the e-mail is valid. If the e-mail is invalid, then we display the error message and don't assign anything to the e-mail property. If the e-mail is valid, then we hide the error message and assign the current value of the

Now, let's write code to display the suggestions drop-down menu for the company
field. Here is the code for this. Place it in the index.js file:
company.flatMap(function(event){
return Bacon.fromPromise($.ajax({url:"/company/ dropdown?companyName=" + encodeURIComponent(event)}));
}).flatMap(function(event){
$("#companies").empty(); return Bacon.fromArray(event);
}).onValue(function(event){
$("#companies").append("<option value=''" + event.company + "''>");
});
Here, whenever the value of company property changes, we make a request to the /company/dropdown path, retrieve the suggestions, and append them to the datalist.
Finally, we need to make a search request whenever a user clicks on the Search
button or hits Enter while in any of the input fields. Here is the code for this.
Place it in the index.js file:
var search_button_click_stream =
$("#search").asEventStream("click");

var search_result_request_stream = Bacon.mergeAll(Bacon.mergeAll([first_name_keypress_stream, last_name_keypress_stream, email_keypress_stream, company_keypress_stream, address_keypress_stream, skill_keypress_stream, search_button_click_stream, dob_keypress_stream]).filter(function(event){
return event.keyCode == 13;
}), search_button_click_stream);
var search_result_request_data = Bacon.combineAsArray([first_name, last_name, email, gender, company, skill, dob, address]).sampledBy(search_result_request_stream).flatMap(function (event){
return event;
});
var search_result_request_cancel = search_result_request_data.filter(function(event){
return event[0] == "" && event[1] == "" && event[2] == "" && event[4] == "" && event[5] == "" && event[6] == "" && event[7]
== "";
}).onValue(function(){
$("#search-result").empty(); alert("Enter enter some data");
});

var search_result_response = search_result_request_data.filter(function(event){
return event[0] != "" || event[1] != "" || event[2] != "" ||
event[4] != "" || event[5] != "" || event[6] != "" || event[7]
!= "";
}).flatMap(function(event){
return Bacon.fromPromise($.ajax({url:"/search?firstName=" + encodeURIComponent(event[0]) + "&lastName=" + encodeURIComponent(event[1]) + "&email=" + encodeURIComponent(event[2]) + "&gender=" + encodeURIComponent(event[3]) + "&company=" + encodeURIComponent(event[4]) + "&address=" + encodeURIComponent(event[7]) + "&skill=" + encodeURIComponent(event[5]) + "&dob=" + encodeURIComponent(event[6]) }));
}).toProperty();

search_result_response.onError(function(){
$("#search-result").empty(); alert("An error occured");
})

search_result_response.flatMap(function(value){
$("#search-result").empty(); return Bacon.fromArray(value);
}).onValue(function(value){ var html = "<li>";

html = html + "<p><b>Name: </b> <span>" + value.first_name + " "
+ value.last_name + "</span></p>";
html = html + "<p><b>Email: </b> <span>" + value.email + "</span></p>";
html = html + "<p><b>Gender: </b> <span>" + value.gender + "</span></p>";
htmt = html + "<p><b>Company: </b> <span>" + value.company + "</span></p>";
html = html + "<p><b>Address: </b> <span>" + value.address + "</span></p>";
html = html + "<p><b>DOB: </b> <span>" + value.dob + "</span></p>";
html = html + "<p><b>Skill: </b> <span>" + value.skill + "</span></p>";
html = html + "</li>";

$("#search-result").append(html);
});

search_result_response.filter(function(value){ return value.length == 0;
}).onValue(function(value){
$("#search-result").empty(); alert("Nothing found")
})
Here is how the code works:

 

Testing the widget

To test the widget, rerun the node app.js command. Now, refresh the localhost:8080 URL.
To test whether the search widget is working, enter Robert in the First Name field and press Enter. You will see this output:
Web design with Functional Reactive Programming

 

To test the company suggestions drop-down menu, enter a in the Company field, and you will see this output:
Web design with Functional Reactive Programming

So now, we have finished building and testing our advanced profile search widget.

Summary

In this recipe, we explored the advanced APIs and concepts of Bacon.js and built a real-world project using them. You should now be comfortable with writing reactive code in a functional manner using Bacon.js and should try integrating Bacon.js into your existing and future projects.
You can also learn more about Bacon.js APIs at https://baconjs.github.io/api.html.

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
Intro to functional reactive programming for advance js web development
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

Here are more hands-on recipes for advance web development:
Build advance single page application with Angular and Bootstrap
Develop microservices with monolithic core via advance Node.JS
Develop server-side applications using microservices with Seneca.JS
Advance UI development with JS MVC framework and react
Develop advance webcam site using Peerjs and Peerserver with Express.JS
Develop advance bidirectional communication site with websockets and Express.JS

This tutorial is developed by Narayan Prusty who is our senior Blockchain instructor.

Related Training Courses

Hands-on Node.JS, MongoDB and Express.js Training
Advance JavaScript, jQuery Using JSON and Ajax
Developing Web Applications Using Angular.JS
Design websites with JavaScript React in 30 hours
Blockchain Certified Solution Architect in 30 hours
Advance JavaScript, jQuery Using JSON and Ajax
Introduction to Python Programming
Object Oriented Programming with UML


Private and Custom Tutoring

We provide private tutoring classes online and offline (at our DC site or your preferred location) with custom curriculum for almost all of our classes for $50 per hour online or $75 per hour in DC. Give us a call or submit our private tutoring registration form to discuss your needs.


View Other Classes!