Concurrency patterns in NodeJS
Introduction
Concurrency means that a program is able to run more than one task at a time. You have already heard that Node.js excels in handling multiple asynchronous I/O operations. But wait, How exactly it is done in Node.js? Isn’t a single-threaded? What about operations other than I/O?
In the article, I’m envisioning to clarify how Node deals with asynchronicity or non-parallel under the hood. I’ll endeavour to explain what are the potential traps that you should be aware of. Let’s dive in!
If Node.js is single threaded then how to handle concurrency ?
Node.js is an open-source, cross-platform runtime environment built on Chrome’s V8 Engine. There are the two main reasons why/how node.js can handle multiple concurrent requests easily and why/how it becomes an perceptible choice for the development of highly scalable and server-side applications.
Node.js uses a single-threaded event loop architecture and it works asynchronously.
But… how?
You can see that Node works reasonably well when it comes to handling I/O operations asynchronously. It might be a little surprising when one knows that it works with just one thread. Doesn’t one thread equals to one operation at the time? Well… yes and no.
That yields another question, “When multi-threaded operations are completed, how does Node know that it’s time to handle them?”. I think you’ll agree that it’s not impressive. So, let’s introduce someone that’ll help Node manage all this mess — Event Loop.
JavaScript Event Loop
Let’s briefly explain what Event Loop is and how it works. Previously, I’ve hinted that you need a kind of “manager” to be able to handle asynchronous operations. That’s precisely what Event Loop does. Let’s take a deep dive into the Event Loop.
The veneer between requests and the event loop is called “Event Queue”. The event queue stores the incoming requests in the order that it received them (in queue). Event loop pull off a request from the queue and passes it on to the internal C++ threads for processing and makes itself available for the other requests, and starts processing those. The event loop is the skeleton in the cupboards with which JavaScript gives us a figment of imagination being multithreaded though it is single-threaded. Then it uses the concept of callback functions from JavaScript to receive the responses of the tasks that were sent to the internal C++ threads earlier for processing and delivers them to the client.
Example:
function sample() {
console.log(“sample number 1”);
setTimeout(function () {
console.log(“sample number 2”);
}, 1500);
console.log(“sample number 3”);
}
console.log(“sample number 4”);
sample();OUTPUT:
sample number 4
sample number 1
sample number 3
sample number 2
Explanation:
Here, the “sample number 4” will get executed first as it is lined even before the function call. Next, the function will be called. Then “sample number 1” will be printed and the application will go into a timeout, for 1.5 seconds (1500 ms). Now, when the application is waiting for 1.5 seconds, it will not block further requests, it will instead process the next request and thus print “sample number 3”. Once the timeout is finished, the lines will now be executed and “sample number 2” will be printed to the console.
Ans so forth, the event loop never stays occupied or blocked by a particular request. This gives Node.js a competitive advantage over traditional web servers as to handling multiple user requests simultaneously. i.e. concurrency.
In asynchronous JavaScript, more than one task will be executed at a time, and this creates async callback.
Callback explained
Callback are functions that are passed as arguments when calling a function, which will start executing code in the background. When the background code finishes, it calls the callback function to let you know the task is completed. A callback’s objective is to execute the code as a talk back to an event. With a callback, you may drill your application to “execute a piece code every time when the event is triggered.”
Example: To execute this code every time the user clicks a key on the keyboard
const button = document.getElementById(‘button’);
function callback(){
console.log(“I am a Button”);
}
button.addEventListener(‘click’, callback);
Explanation:
In the above code, we could see addEventListener as a function and we are passing callback as an argument. And when the button is clicked (an event is triggered) the addEventListener registers the callback function.
Instead of immediately returning some result like most functions, functions that use callbacks take some time to produce a result. The word ‘asynchronous’, alias ‘async’ just signifies ‘takes some time’ or ‘be it in the future, not right now’. Usually Callbacks are only used when doing I/O operations, e.g. on any downloading , while reading files or interacting with databases, etc.
Here, as the above example does a single print of the word mentioned. But in real-time application, we do not see this coming. Instead, When multiple asynchronous functions are chained together then callback hell situation comes up.
How do I fix callback hell?
There are many ways to handle finite and nested callbacks. It can be using async await or using the old school way of promise or splitting the code into different functions or using generators or with a library RxJS.
Using Promise
We generate a new promise for each callback in order to convert them to promises. When the callback is successful, we may resolve the promise and when the callback fails, we reject the promise.
Example: To create a promise on receiving user data
function getPromise {
const newUser = getUser(user);
return new Promise((resolve, reject) => {
if (user)
resolve(user)
} else {
reject(new Error(‘We don’t have a new user!’))
Now, can create one more function and call the above function as a callback.
Using Async-Await
We can write asynchronous functions as if they are synchronous and executed sequentially with the use of await as it stops the execution until the promise is resolved i.e., function execution is successful.
Example: To fetch and update the user profile
const userProfile = async () => {
// argument indicated number of users to fetch
const user = await fetchUsers(1)
const updatedAddress = await updateAddress(user);
const phonenumber = await getPhonenumber ();
const updateUser = await updateUser(user, updatedAdress, phonenumber);
return user;
}
// fetch and update user profile
userProfile()
Using Generators
So, let’s start with “sequential”. This is the part you should already be familiar with. It’s another way of talking about single-threaded behaviour and the sync-looking code that we get from ES6 generators.
Example: To yield an output sequentially.
function *main() {
var x = yield 100;
var y = yield 3;
var z = yield (y * 5);
}
OUTPUT:
10
3
15
Each of those statements is executed sequentially one at a time. The yield
keyword annotates where a blocking pause (blocking only in the sense of the generator code itself, not the surrounding program!) may occur in the code, but that doesn't change anything about the top-down handling of the code inside *main()
. Easy enough to understand, right?
Using RxJS/ Reactive Programming
Reactive programming is about creating responsive and event-driven applications, where an observable event stream is pushed to subscribers, which observe and handle the events. RxJS is a library for authoring asynchronous and event-based programs by using observables and operators.
We can stage async data streams with Observables, query async data streams using Operators, and tune the concurrency in the async data streams using Schedulers. Simply put, Rx = Observables + Operators + Schedulers.
Getting familiar with RxJS terminology
We need to observe data, which means that there is a data producer and it can be a server sending data using HTTP or an input field where the user enters some data. An observable is a function or an object on the client that gets the producer’s data and pushes them to the subscriber(s). An observer is an object or a function that handles the data elements pushed by the observable. Guess the above pictorial representation would explain a lot better.
Players of RxJS
* Observable — data stream that pushes data over time
* Observer — consumer of an observable stream
* Subscriber — connects observer with observable
* Operator — a function for the proceeding data transformation
Let’s create an observable that will emit 10, 20, and 30 and subscribe to this observable:
Example: To subscribe to an observable Event.
Rx.Observable.of(10,20,30)
. subscribe(
value => console.log(value),
err => console.error(err),
() => console.log (“End of streaming”)
);OUTPUT:
10
20
30
End of streaming.
Conclusion
Concurrent code is better than sequential code because it is nonblocking and can accommodate several requests concurrently with minimal issues. This article walked you through on how to deal with concurrency using JavaScript callbacks in Node. While, RxJS technique is handled in the React framework by subscribing to the observer. Now, decide wise to handle concurrent requests without ending up in callback hell.