Asynchronous Programming In Node JS
A guide to help you understand the asynchronous behaviour of Node JS.
Being a backend developer, I understand how people tend to get confused between the asynchronous and synchronous behaviour of NodeJS. Through this blog I will explain the asynchronous behaviour of NodeJS and answer all your HOWs and WHYs!
Drop off the confusion and read along. Let’s get started!
If you read any article about NodeJS , you will find something very common coming up . These four terms describe the nature of NodeJS- Asynchronous, Non blocking, Single threaded and Event driven. In Node programming model almost everything is done asynchronously but many of the functions in NodeJS core have both synchronous and asynchronous versions.
Let’s first understand what a synchronous programming model is.
SYNCHRONOUS PROGRAMMING MODEL
In synchronous programming, one line runs after the next regardless of how long each line takes to execute.
A very basic and simple example is:
console.log("start")
console.log("end")
The output will first print start then it will print end. This shows the sequential execution of code.
Let’s make it clearer with another example:
var fs = require('fs');
var content = fs.readFileSync("file.txt", "utf8");
console.log(content);
console.log("Last One");
readFileSync() will read the entire file and store it into the memory and then go to print the data and a message in the console (see the output). This is the synchronous version, everything is paused until the file is finished reading.
ASYNCHRONOUS PROGRAMMING MODEL
JavaScript code is executed on a single thread within a computer process. Its code is processed synchronously on this thread, with only one instruction run at a time. Therefore, if we were to do a long-running task on this thread, all of the remaining code is blocked until the task is complete. By leveraging JavaScript’s asynchronous programming features, we can offload long-running tasks to a background thread to avoid this problem. When the task is complete, the code we need to process the task’s data is put back on the main single thread.
In asynchronous programming, we can also execute the other code while we wait for long activities to run completely.
Example: Fetching data for a user from database.
Let’s see the same example of reading a file that we saw above, now using an asynchronous model:
var fs = require('fs');
fs.readFile("file.txt", "utf8", function(err, content) {
if (err) {
return console.log(err);
}
console.log(content);
});
console.log("Last One");
This is the asynchronous version. Here the system is ready for performing another task. Instead, the callback function gets called when the file is finished reading. See the output, it first executes the last command and prints Last One, then it prints the contents of the file. This is because reading the contents from the file will take some amount of time, during that interval the next lines of code will be executed. This shows the non-blocking nature of NodeJS.
Asynchronous Programming with Callbacks
A callback function is one that is passed as an argument to another function, and then executed when the other function is finished. We use callbacks to ensure that code is executed only after an asynchronous operation is completed.
If you observe in the above code then, readFile() is taking a third parameter which is the callback function. And inside that callback function we are displaying the content read from the file. So callback will be executed as soon as the file reading process is complete and then the content of file will be displayed.
Let’s make the understanding of asynchronous behaviour clearer and dive deep into answering all the “HOW” and “WHY” questions.
The Event Loop
Some important terms associated with working of event loop are-
- Call Stack- It is a list-like data structure where items can only be added to the top, and removed from the top. Its job is to track the execution of the program and it does that by keeping track of the functions that are currently running.
- Storage Table (sometimes called Node APIs controller)- This table stores the operation, the condition for it to be completed, and the function to be called when it’s completed.
- Callback Queue- This queue is another list-like data structure where items can only be added to the bottom but removed from the top. Functions in the callback queue are waiting to be added to the call stack.
The combined effect of the call stack, callback queue, and event loop allows JavaScript code to be processed while managing asynchronous activities. Let’s see how it works with the help of code.
console.log('Starting');setTimeout(()=>{console.log("2 second timer")},2000);console.log("Ending");
- First thing that happens is that main() gets added to the call stack that starts the execution of our program.
- Then the control reaches the first line of the code, which is a console.log() command. Since it is a function so it gets added to the call stack. Then the output showing “Starting” is displayed and after that console.log() gets removed from the call stack.
- Moving to the next line we see, setTimeout(…, 2000) which is also a function. So it will also be pushed to the call stack. setTimeout() is not a part of the javascript programming language, instead it is NodeJS which creates an implementation of setTimeout() using C++ and provides it to the NodeJS scripts to use. It is basically an asynchronous way to wait for a specific amount of time and then have a function run. When we call setTimeout() its actually registering an event with NodeJS APIs and there is an event callback pair, where the event is simply to wait 2 seconds and the callback is the function to run.
- While we are waiting for those two seconds to happen we can do other stuff inside of the call stack. So now the last line of the code which is a console.log() command will now be added to the call stack to get executed. The output — “Ending” will be displayed and after that console.log() will be removed from the stack. Since now the code is over so main() will be removed from the stack and now the stack is empty.
- Now when the 2 seconds are over the callback is ready to be executed. For that it will be added to the callback queue. Before the callback gets executed it needs to be added on to the call stack, that’s where functions go to run.
- Here the event loop comes into play. It looks at two things- the call stack and the callback queue. If it finds the call stack to be empty then it will run items from the callback queue. Since now the call stack is empty, so the callback function will be added to the call stack from callback queue and then it will be executed. This callback function contains a console.log() command which will first be added to the call stack, then the output — “2 second timer” will be displayed and then it will be removed from the stack.
This is how we got such an output.
This similar example instead of setTimeout() can be considered as an operation to fetch the details of n people from database , which has a callback function that can be used to print the fetched data. The callback function gets executed only when the data fetching operation is completed.
Concurrency and Throughput
JavaScript execution in NodeJS is single threaded, so concurrency refers to the event loop’s capacity to execute JavaScript callback functions after completing other work. Any code that is expected to run in a concurrent manner must allow the event loop to continue running as non-JavaScript operations, like I/O, are occurring.
As an example, let’s consider a case where each request to a web server takes 50 ms to complete and 45 ms of that 50 ms is database I/O that can be done asynchronously. Choosing non-blocking asynchronous operations frees up that 45 ms per request to handle other requests. This is a significant difference in capacity just by choosing to use non-blocking methods instead of blocking methods.
I hope after reading this blog you feel more confident and knowledgeable about Asynchronous Programming In NodeJS.
This is not the end…
I’m working on more blogs to help you out.