Dilip Thakur
7 min readJun 16, 2020

Concept of Synchronous & Asynchronous Programming in JavaScript — Part 2 [Callbacks and Promises]

In Part 1 of this article, I mentioned about the basics of Synchronous and Asynchronous Programming in JavaScript. I strongly recommend you to go through Concept of Synchronous & Asynchronous Programming in JavaScript — Part 1 if you are new or have any confusions regarding the basics.

According to wikipedia: “Asynchrony in Computer Programming refers to the occurence of events independently of the main program flow and way to deal with such events.”

In this part, we will be discussing about how Callbacks and Promises handles Asynchronous Tasks in JavaScript.

Callbacks :

We will be starting with the basics of Callbacks. I will try to cover the basics with the help of following examples :

// add two numbers and return the double of the resultfunction addAndDouble(x, y) {
return (x+y)*2
}
const result = addAndDouble(2,3)
console.log("Result = ", result)

Now let’s perform the same task using the concept of Callbacks .

Here’s what it would look like :

/* add two numbers and return the double of the result using callbacks */function makeDouble(num) {
return num*2
}
function add(x, y, callback){
const sum = x+y
const result = callback(sum)
console.log("Result = ", result)
}
add(2, 3, makeDouble)

Here, if we run any of the above example, we would get the same output :

Result = 10

Although the second example looks confusing, but both does the same thing. The only difference is , in second example we are using the concept of Callbacks . Being said that, let’s highlight some of the important concepts of JS regarding Callbacks based on what we did.

As you can pass any string or number as argument to a function in JS, you can also pass a reference to a function as argument. When you do this, the function you are passing as an argument is called Callback Function and the function you are the passing the Callback Function to is called a Higher Order Function.

In our example above, makeDouble() is a Callback Function which is being passed to add() as an argument and thus add() here is the Higher Order Function.

The Callback Function is not run unless called by its containing. It is called back in the Higher Order Function that’s containing it. Hence the term “Callback Function” .

Here in our example, the Callback Function i.e., makeDouble() is being called after the addition of x and y is executed.

What we can conclude from this is , “Callbacks are just the functions passed in as an argument which you want them to be called after some operation is done.”

Note: Callbacks can be a named or anonymous function. Also, we can pass multiple parameters to the callback function depending on the function.

Now let’s move a step forward focusing on how Callback handles asynchronous tasks. For that, let’s create a situation that shows the necessity of Callbacks.

“I will be using setTimeout() so as to resemble API Calls or any task that might take some time to be executed”

const todos = ["Todo1" , "Todo2"]function getTodos() {
setTimeout(()=>{
todos.forEach((todo) => {
console.log(todo)
})
},1000)
}
function addNewTodo(newTodo) {
setTimeout(() => {
todos.push(newTodo)
}, 2000)
}
addNewTodo("Todo3")
getTodos()

If you run this block of code, then to your surprise, even after placing addNewTodo() before getTodos(), the output will be :

Todo1
Todo2

Where is “Todo3” then ???

The reason that we don’t see “Todo3” in the output is, the addNewTodo() took longer that getTodos() to execute because of the time specified in setTimeout() . This is a likely situation one might face while doing asynchronous tasks. To handle such issues, we need Asynchronous Programming.

Callbacks are simple functions which are used to notify the calling instance when an asynchronous code block has been executed and the result is available. So, let’s actually make this work using Callbacks which is one of a way to handle such issues while doing Asynchronous Tasks.

In this case, we want to add the new todo first and then list the todos so as to get the recently added todo as well. So, we will make getTodos() a Callback Function that needs to be executed after addNewTodo() finishes its tasks.

Here’s how it would look like :

// using concept of Callback const todos = ["Todo1" , "Todo2"]function getTodos() {
setTimeout(() => {
todos.forEach((todo) => {
console.log(todo)
})
},1000)
}
function addNewTodo(newTodo, callback) {
setTimeout(() => {
todos.push(newTodo)
callback()
}, 2000)
}
addNewTodo("Todo3", getTodos)

If you run this now, you will get :

Todo1
Todo2
Todo3

We got our “Todo3” back, Yayyyyyyyyy !!!!
What actually happened here is, our callback function i.e., getTodos() waited 2 seconds while the new todo item was being added to the list before it’s execution thus giving the above output.

This is fairly simple, as all we need to do is pass in a function which we want to execute after the Asynchronous Operation is done. But the major problem introduced by this approach is when we want to do Multiple Asynchronous Calls that too sequentially. Or, what if we want to use the result to make another request sequentially ????

Well, it introduces what is popularly known as “Callback Hell” and would look somewhat like below :

function addNewTodo(newTodo) {
addDate(date){
addLocation(location){
addAlarm(alarm){
......
}
}
}
}

This will actually work but will be very hard to debug and maintain. This situation known as “Callback Hell” can arise while working with large sets and to resolve such challenges of Callbacks, Promises were introduced.

Promises :

“I Promise to do this whenever that is true. If it isn’t true, then I won’t”
Though it sounds like an If Statement, there’s a huge difference that you will find out after reading this post.

Promises are simply the best wrappers to asynchronous tasks. A Promise is used to handle the asynchronous result of an operation. With Promises, we can defer execution of a code block until an async request is completed. This way, other operations can keep running without interruption.

Every Promise Constructor expects a function which has two parameters resolve and reject . resolve is called if the Promise is resolved successfully and reject is called if any error has occurred.

Let’s look into a simple example on how to create a Promise .

// creating a promise using regular functionconst promise = new Promise(function(resolve, reject) {
// an api call or any async operation
})
// creating a promise using arrow functionconst promise = new Promise((resolve, reject) => {
// an api call or any async operation
})

You can create a promise using the keyword Promise as mentioned above.

To know about it’s usage, let’s jump into another example .

const success = true// creating a promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if(success){
resolve("It's a success")
} else {
reject(Error("Failed"))
}
},3000)
})
// using the created promise
promise.then((result) => {
console.log("Result = ", result)
}).catch((error) => {
console.log("Error = ", error)
})

Running the above example will yield the following output :

Result = It's a success

Also, if you alter the value of success to false , the reject() will be called hence giving the following output :

Error = Error: Failed

What this conveys is , .then() receives a function with an argument which is the resolve value of our Promise and .catch() receives the reject value of our Promise.

During all this process, you can notice how the [PromiseStatus] changes to “resolved” form “pending” after the completion of the task (either “resolved” or “rejected”). That’s how Promises handles the async tasks.

As a next step to clear the confusions regarding the implementation of Promises, we will redo the previous task, where we added a new todo and listed them, using Promises instead of Callbacks .

const todos = ["Todo1" , "Todo2"]function getTodos() {
setTimeout(()=>{
todos.forEach((todo) => {
console.log(todo)
})
},1000)
}
function addNewTodo(newTodo) {
return new Promise((resolve, reject) => {
setTimeout(() => {
todos.push(newTodo)
const error = false;

if(!error) {
resolve("Added Successfully")
} else {
reject("Something went wrong")
}
}, 2000)
})
}
addNewTodo("Todo3").then((response) => {
console.log(response)
getTodos();
}).catch((error) => {
console.log("Error = ", error)
})

And if you run the program, you will get the following output :

Added Successfully
Todo1
Todo2
Todo3

We can see the “Todo3” here as well.

That’s how we can convert a program that’s using Callback concept to use Promises to get the same result but with more elegant and maintainable code as compared to that while using Callbacks.

Lastly, taking about the solution to the Callback Hell problem, while having to execute two or more asynchronous operations based on the result of preceding Promises, we can always use the concept of Chaining Promises.

To show the usage of Chaining Promises, let’s redo our first ever example of this article (add two numbers and return the double of the result) using Promise. To resemble the current scenario, we will have two Promises, one to add 2 numbers and the other to double it. We will be chaining these two Promises together to get the output as expected.

Here’s how it’s done :

/* use of promise chaining - while having to execute two or more asynchronous operations in sequence */const add = (x, y) => {
return new Promise((resolve, reject) => {
const error = false;
if(!error){
resolve(x+y)
}else{
reject("Something went wrong")
}
})
}
const returnDouble = (sum) => {
return Promise.resolve(sum*2)
}
add(2,3).then((value) => {
return returnDouble(value)
}).then((result) => {
console.log("Result = ", result)
}).catch((error) => {
console.log("Error : ", error)
})

The execution of above block of code will yield the following output :

Result = 10

This is exactly what we expected.

That’s how Promises have made it more easy to write async code and avoid Callback Hell, but can we do better ??? Because, just like you might have been thinking, it still looks confusing, isn’t it ??
To that, I would say, Oh Yeah ! definitely with “Async and Await” about which I will be writing in the final part of this article, Part 3. So stay tuned for the final part which will be posted soon. 😉 😉 😉

I really hope you enjoyed reading this article and it helped you to to understand the concept of Callbacks and Promises. I really look forward to hear from you guys regarding any improvement or queries you have. More than happy to take the comments and suggestions. 🙂

Happy Coding !!!

“Life is asynchronous. You need make promises to handle it better. And remember, you have better options as well — have patience. Await !!!!” 😉 😉 😉