Patterns for Dealing with Asynchronous Code in NodeJS
NodeJs Architecture
The architecture that NodeJs follows is Non-Blocking or Asynchronous. In Nodejs single thread will handle multiple requests and response.
Let's see example code of how asynchronous operations works in NodeJS
console.log('after');
setTimeout(() => {
console.log('Reading product details from database');
}, 2000); // This function will execute after 2 seconds
console.log('before');
/* Output of the above code is
after
before
Reading product details from the database*/
Issues with asynchronous code are
console.log('after');
const productDetails = getProductDetails('12345');
console.log(productDetails); // This will print undefined in console
console.log('before');
function getProductDetails(productId) {
setTimeout(() => {
console.log('Reading product details from database');
return {'id': productId, 'name': 'Chocolate'};
}, 2000);
}
/* Output of the above code is
after
before
Undefined*/
The reason behind this output is, The function which we defined in the code will return output after 2 seconds. That's why we get undefined in the output.
To overcome this issue there are 3 patterns available to handle asynchronous code they are
- Callbacks
- Promises
- async/await
** Callbacks **: Callback is a function that we are going to call when the result of the asynchronous operation is completed.
console.log('after');
getProductDetails('12345', (err, productDetails) => {
console.log(productDetails)
});
console.log('before');
function getProductDetails(productId, cb) {
setTimeout(() => {
console.log('Reading product details from database');
cb(null, {'id': productId, 'name': 'Chocolate'});
}, 2000);
}
/* Output of the above code is
after
before
{'id': 12345, 'name': 'Chocolate'}*/
Callback hell
If there are multiple Asynchronous operations we will hold up with callback hell issue. The structure will look like this.
console.log('after');
getProductDetails('12345', (err, productDetails) => {
getImages(productDetails.name, (xerr, productImages) => {
getRecommendations(productDetails.name, (uerr, productResults){
//Callback hell Issue
})
})
});
console.log('before');
function getProductDetails(productId, cb) {
setTimeout(() => {
console.log('Reading product details from database');
cb(null, {'id': productId, 'name': 'Chocolate'});
}, 2000);
}
function getImages(productId, cb) {
setTimeout(() => {
console.log('Reading product Images of product from database');
cb(null, {'id': productId, 'imageDetails': ['ImageURL1','ImageURL2']});
}, 2000);
}
function getRecommendations(productId, cb) {
setTimeout(() => {
console.log('Product recommendations');
cb(null, {'id': productId, 'imageDetails': ['Product1','Product2']});
}, 2000);
}
The better way to get rid of callback hell is Promises which is introduced in ES6.
Promises:
A promise is an object ** which holds the eventual result of an asynchronous operation**. When we are creating a Promise object it will be in the pending state after completion of the asynchronous operation, It can be either fulfilled if there is no error or in the rejected state
console.log('after');
getProductDetails(1)
.then(productDetails => getImages(productDetails.name))
.then(productImages => getRecommendations(productDetails.name))
.then(Recommendations => console.log('Recommendations', Recommendations))
.catch(err => console.log(err));
function getProductDetails(id){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve({'id': productId, 'name': 'Chocolate'});
},2000);
})
}
function getImages(name){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve({'id': productId, 'imageDetails': ['ImageURL1','ImageURL2']});
},2000);
})
}
function getRecommendations(name){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve({'id': productId, 'imageDetails': ['Product1','Product2']);
},2000);
})
}
Async/Await approach:
Async/Await helps you to write asynchronous code synchronously. It is built on the top of promises
console.log('after');
async function displayCommits(){
try{
const productDetails = await getProductDetails('1234');
const productImages = await getImages(productDetails.name);
const recommendations = await getRecommendations(productDetails.name);
console.log('recommendations', recommendations);
}
catch{
console.log('Error', err.mesage);
}
}
function getProductDetails(id){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve({'id': productId, 'name': 'Chocolate'});
},2000);
})
}
function getImages(productName){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve({'id': productId, 'imageDetails': ['ImageURL1','ImageURL2']});
},2000);
})
}
function getRecommendations(productName){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve({'id': productId, 'imageDetails': ['Product1','Product2']);
},2000);
})
}
These three are the patterns to handles asynchronous operations in NodeJS code.