Clean Code NodeJs : Execute Asynchronous Tasks in Series

Haider Malik
CloudBoost
Published in
9 min readOct 5, 2017

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

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Responses (3)

What are your thoughts?