Clean Code NodeJs : Execute Asynchronous Tasks in Series
What is Callback?
Callback is an asynchronous equivalent for a function. A callback function is called at the completion of a given task.
function login(callback) {
//do some operations
callback(‘You have successfully LoggedIn’);
}
login(function(result) {
console.log(result);
});
I have created a login function. This login function is accepting one argument which is the callback. I am invoking the callback function after the completion of login method. It will immediately log the result to the console.
Use Case
I am going to show you how to implement this use case in node.js. How you can write clean code and execute asynchronous tasks in series.
What is Callback-Hell?
function seriesCallbackHell() {
const company = new Company({
name: 'FullStackHour'
});
company.save((err, savedCmp) => {
if (err) {
return next(err);
}
const job = new Job({
title: 'Node.js Developer',
_company: savedCmp._id
});
job.save((err, savedJob) => {
if (err) {
return next(err);
}
const application = new Application({
_job: savedJob._id,
_company: savedCmp._id
});
application.save((err, savedApp) => {
if (err) {
return next(err);
}
const licence = new Licence({
name: 'FREE',
_application: savedApp._id
});
licence.save((err, savedLic) => {
if (err) {
return next(err);
}
return res.json({
company: savedCmp,
job: savedJob,
application: savedApp,
licence: savedLic
});
});
});
});
});
}
If there are heavily nested callbacks in your code, It will become a callback-hell. You can see that it’s tough to read and maintain the code. It’s complicated to debug.
Now I am going to show you how to avoid callback-hell by using three design patterns in Node.js.
Execute Tasks In Series Using Async.series
First of all I am going to show you to execute asynchronous functions in series by using callbacks.If you want to use callbacks, you must use Async.js third party npm library.Now I need to use Async.series method.
function seriesDemo(req, res, next) {
let rsp = {};
const tasks = [
function createCompany(cb) {
const company = new Company({
name: 'FullStackhour'
});
company.save(function(err, savedCompany) {
if (err) {
return cb(err);
}
rsp.company = savedCompany;
return cb(null, savedCompany);
});
},
function createJob(cb) {
const job = new Job({
title: 'Node.js Developer',
_company: rsp.company._id
});
job.save((err, savedJob) => {
if (err) {
return cb(err);
}
rsp.job = savedJob;
return cb(null, savedJob);
})
},
function createApplication(cb) {
const application = new Application({
_job: rsp.job._id,
_company: rsp.company._id
});
application.save((err, savedApp) => {
if (err) {
return cb(err);
}
rsp.application = savedApp;
return cb(null, savedApp);
})
},
function createLicence(cb) {
const licence = new Licence({
name: 'FREE',
_application: rsp.application._id
});
licence.save((err, savedLic) => {
if (err) {
return cb(err);
}
return cb(null, savedLic);
})
}
];
async.series(tasks, (err, results) => {
if (err) {
return next(err);
}
return res.json(results);
})
}
Async.series takes the collection of asynchronous functions and optional callback method. When all the tasks complete the execution, then the final callback will be called and return the results to the server. This result variable will hold the array of the item with the company, job, application, and licence object.
If something went wrong in any function, then it will immediately call the final callback function with error value and the rest of the methods will not run.
Execute Tasks In Series Using Async.waterfall
If your asynchronous tasks are dependent on each other or the next function needs to use the result of the previous method, then you have to use the async.waterfall approach. The significant difference between waterfall and series is that waterfall allows you to send the result to the next callback. Let me show you the example of Async.waterfall.
function waterfallDemo(req, res, next) {
const tasks = [
function createCompany(cb) {
const company = new Company({
name: 'FullStackhour'
});
company.save(function(err, savedCompany) {
if (err) {
return cb(err);
}
return cb(null, savedCompany);
});
},
function createJob(company, cb) {
const job = new Job({
title: 'Node.js Developer',
_company: company._id
});
job.save((err, savedJob) => {
if (err) {
return cb(err);
}
return cb(null, {
job: savedJob,
company
});
});
},
function createApplication(result, cb) {
const application = new Application({
_job: result.job._id,
_company: result.company._id
});
application.save((err, savedApp) => {
if (err) {
return cb(err);
}
return cb(null, {
job: result.job,
company: result.company,
application: savedApp
});
})
},
function createLicence(result, cb) {
const licence = new Licence({
name: 'FREE',
_application: result.application._id
});
licence.save((err, savedLic) => {
if (err) {
return cb(err);
}
return cb(null, {
job: result.job,
company: result.company,
application: result.application,
licence: savedLic
});
})
}
];
async.waterfall(tasks, (err, results) => {
if (err) {
return next(err);
}
return res.json(results);
})
}
You can see that createJob(company, cb) is dependent on the createCompany(cb). CreateJob function needs the id of the Company to save the job. Similarly, createApplication(result,cb) is reliant on the createJob task. In this way, you can run tasks in series and send the result to the next callback by using Async.waterfall.
What is a Promise
A Promise is an Object that may produce single value sometimes in the future either a resolved or a reason that is not resolved.Imagine that your friend is going to commit you he/she will go for a movie with you next week. A Promise has three states.
Pending
You don’t your friend will go to a movie with you or not.
Resolved
If promise has been resolved it means your friend is ready to watch a movie with you.
Rejected
If Promise has rejected he/she will not happy to go to a movie with you.Maybe your friend is busy with other activities.
const isFriendReady = true;
// Promise
const watchMovie = new Promise((resolve, reject) => {
if (isFriendReady) {
return resolve('You are going to watch a Fast Furious 8');
} else {
var reason = new Error('Your friend is not Ready');
return reject(reason);
}
});
// call our promise
const askFriend = () => {
watchMovie
.then((fulfilled) => {
// Great!,You are going to watch a movie with your friend
console.log(fulfilled);
})
.catch(error => {
// ops, you're friend is not ready :o
console.log(error.message);
});
}
askFriend();
I have created a watchMovie Promise. Promise always accepts executor function as an argument. In this executor function you have to pass the resolve and reject. The resolve and reject both are functions.
If Promise completes or executes successfully then, resolve function will be called with resolve value
If Promise is not completed successfully, it will call the reject method.
I am consuming a Promise in askFreind method. If Promise has resolved successfully then method will be called with You are going to watch a Fast Furious 8
If Promise rejected, catch method will be called with this reason Your friend is not Ready
Execute Tasks in Series Using Promise Chaining
const promiseChaining = (req, res, next) => {
let rsp = {};
const company = new Company({
name: 'FullStackhour'
});
company.save()
.then(savedCompany => {
rsp.company = savedCompany;
const job = new Job({
title: 'Node.js Developer',
_company: rsp.company._id
});
return job.save();
})
.then(savedJob => {
const application = new Application({
_job: savedJob._id,
_company: rsp.company._id
});
rsp.job = savedJob;
return application.save();
})
.then(savedApp => {
const licence = new Licence({
name: 'FREE',
_application: savedApp._id
});
rsp.application = savedApp;
return licence.save();
})
.then(savedLic => {
rsp.licence = savedLic;
return res.json(rsp);
})
.catch(err => {
return next(err);
})
}
If you want to run tasks in series. You can use Promises Chaining. First of all it will create a new record in company model. The result will be returned to the next then method. Then method always returns a promise. When this promise will be resolved the result will catch in the next then chaining method.
If something went wrong in any method, catch method immediately called with error value.
What is Async/Await
Async await has been introduced in node version 7.8. Some developers are using node version 6 or version 5. You can still use async await but you have to install the package async/await. I am using node version 7.8
let isFriendReady = true;const watchMovie = new Promise((resolve, reject) => {
if (isFriendReady) {
return resolve('You are going to watch a Fast Furious 8');
} else {
var reason = new Error('Your friend is not Ready');
return reject(reason);
}
});const askFriend = async() => {
const result = await watchMovie;
return result;
}askFriend()
.then(result => {
console.log(result);
})
.catch(err => {
console.error(err);
})
First of all, you need to create a new asynchronous function.We have created asynchronous method with this async keyword it means now you can use await expression in this function. You can apply await expression on promises. Now we have only one promise watchMovie, apply await expression to watchMovie. The await operator is used to wait for promise.
This await operator returns the resolved value of promise and await expression causes the function execution to wait for the promise resolution and to resume the async function execution.When the promise has been resolved and this resolved value saves to result variable. I am also returning the result from the askFriend method.
Async function always returns a promise. You know how to consume a promise. When askFriend promise has been resolved then method will be called with result value.
Execute tasks In Series Using Async/Await
function seriesDemo(req, res, next) {
const saveRequest = async() => {
const company = new Company({
name: 'FullStackhour'
});
const savedCompany = await company.save();
const job = new Job({
title: 'Node.js Developer',
_company: savedCompany._id
});
const savedJob = await job.save();
const application = new Application({
_job: savedJob._id,
_company: savedCompany._id
});
const savedApp = await application.save();
const licence = new Licence({
name: 'FREE',
_application: savedApp._id
});
const savedLic = await licence.save();
return {
company: savedCompany,
job: savedJob,
application: savedApp,
savedLic: licence
};
}
saveRequest()
.then(result => {
return res.json(result);
})
.catch(err => next(err));
}
Async/Await is one of the best way to write Asynchronous code in Node.js. You can see that code looks very concise and clean. You can read, maintain and debug the code very easily. I highly recommend you to run functions in series by using Async/Await.
Bonus:
Buy any course with $10. I have attached $10 couponCode with each course.
Source Code: https://github.com/HaiderMalik12/series_tasks_node