Callbacks, Promises, Async & Await in Javascript

Miraj Hamid
8 min readSep 5, 2021

Recently I have been working with some NodeJs projects and found there are some interesting parts that I could share with you in the above-mentioned keywords in Js world. Here I am going to share some of the basic concepts and experience that I gained with Callbacks, Promises, Async & Await in Javascript.

This will be a lengthy read, but I am sure you won’t regret it as I simply explain each concept and syntax as simply as possible.

As you already know Javascript, runs in a single-threaded event loop, and in the first loop run it will execute all of the synchronous code and it queues up asynchronous events such as fetch some data from the network to be called back later. But in some situations, we may want to do the execution of the (N+1) task to be called or held until the Nth task is fully completed (Nth task may be an API call response or big calculation). In that case, JS has given some options to address and handle these situations in asynchronous events.

To understand the synchronous and asynchronous behavior of javascript it is required to understand the tasks that are handled by the event loop. There are two tasks in the event loop, they are microtasks and macrotasks. The behavior of these tasks is different. For example, macrotasks such as setTimeout will be executed in the next event loop while microtasks such as a fulfillment of a promise will be callback before the start of the next event loop.

console.log('sync console event 1');setTimeout(function(){ console.log('setTimeout for 0 milliseconds event in next event loop' ) } , 0);Promise.resolve().then(_ => console.log('promise resolve event before the next event loop'));console.log('sync console event 2');
The output

Now you have the idea of the event loop, macrotasks, and microtasks. Assume now we want to console log sync console event 2 after setTimeout event? It is not possible with the normal event-loop behavior. Okay, Let's get back to the topic.

Callbacks

Callbacks are the functions or events that execute after some event took place. As given in the above code example. cosole.log function Inside the setTimeout function is itself a callback function that is executed after 0 milliseconds.

Now let’s discuss what are the actual use cases of callback functions. Please look into the below code example and its execution result.

console.log('start');const logInUserFunc = (username, password) => {setTimeout(() => {console.log('function api response');return ({user: username});}, 3000);}const userLoginRes = logInUserFunc('miraj','123#');console.log(userLoginRes);console.log('end');
The output

as you may discover the expectation of console logging of the user information has not been met. That is because the synchronous events have been executed in the event loop when the time of setTimeout execution starts after a 3s delay. To overcome this we can use the callback function where callback function is a function passed into another function as an argument to be executed later.

console.log('start');const logInUserFunc = (username, password, callback) => {setTimeout(() => {
console.log('function api response');
callback({user: username});
}, 3000);
}const mycallbackFunc = (user) => {console.log(user);}const userLoginRes = logInUserFunc('miraj','123#',mycallbackFunc);
console.log('end');
The output

As you see with the help of callback function mycallbackFunc it is possible to execute the console log after the timeout of 3s has taken place. Here we have solved one problem which is calling back a function or event after some event, but in the actual world, we may have a sequence of functions to be called in this way. Thus some functions might fail at the middle of the sequence assume a DB call or an API call could fail. It is hard to write another callback function to be executed if fail and so and so. Then this approach would not be that handy. To address that promise came into the field. Let’s move to see what is promise in Js

Promise

As the name suggests it is a promise of an event. According to the MDN definition

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

A JavaScript Promise object contains both the producing code and calls to the consuming code. A Promise is in one of these states:

  • pending: initial state, neither fulfilled nor rejected.
  • fulfilled: meaning that the operation was completed successfully.
  • rejected: meaning that the operation failed.

A pending promise can either be fulfilled with a value or rejected with a reason (error). When either of these options happens, the associated handlers queued up by promise’s then or catchwill be executed accordingly.

As you see in the code below we have simply achieved the success and failure executions accordingly with minimal changes and high readability compared to the callback approach.

const mypromise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("function api response back");
if (Math.random() < 0.9) {
resolve({ user: "miraj" });
} else {
reject(new Error("some error"));
}
}, 1);
});
mypromise
.then((res) => {
console.log(res);
return "something";
})
.then((res) => {
console.log(res);
})
.catch((err) => console.error(err));
The output

in the above code, myPromise is the promise and the after execution of setTimeout’s callback, if it is a success it will resolve the response, or if a failure it will reject with an error. After the execution of myPromise, according to the response, it will pass the data back into the next .then or catch.

It is also possible to handle each then with it’s own reject function as shown below where handleResolvedA,handleResolvedB,handleResolvedChandles success scenarios of the above response and handleRejectedA, handleRejectedB,handleRejectedChandles the failures accordingly.

const mypromise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("function api response back");
if (Math.random() < 0.9) {
resolve({ user: "miraj" });
} else {
reject(new Error("some error"));
}
}, 1);
});

myPromise
.then(handleResolvedA, handleRejectedA)
.then(handleResolvedB, handleRejectedB)
.then(handleResolvedC, handleRejectedC);

Now let's see how to modify our logInUserFunc with what we have learned above.

const logInUserFunc = (username, password) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("function api response back");
if (Math.random() < 0.9) {
resolve({ user: username });
} else {
reject(new Error("some error"));
}
}, 3000);
});
};
logInUserFunc("miraj", "123#")
.then((res) => {
console.log(res);
return "something";
})
.then((res) => {
console.log(res);
})
.catch((err) => console.error(err));
The output

Say that we have multiple promises to be executed in parallel, then the option is Promise.all let’s see how to do that in the code below.

The output

Please note that the entire result would not be resolved in this approach if one of the promises in the array takes time even though others have completed it.

So now as you can see we have to add so many .then which makes our code a little bit complex (not complex as callback approach). To overcome that complexity and write the code in a synchronous way Js introduced async. Let’s find out what is async.

Async and Await

With async and await we can code asynchronous execution in a way where we write synchronous code. Simply synchronous look but asynchronous flavor. Let’s have a look at these two keywords.

We have the “async” keyword, this keyword which you put in front of a function declaration to turn it into an async function. An async function is a function that knows how to expect the possibility of the await keyword being used to invoke asynchronous code.

The advantage of an async function only becomes apparent when you combine it with the await keyword. The “ await keyword only works inside async functions within regular JavaScript code. However, it can be used on its own with Javascript modules. This await keyword can be put in front of any async promise-based function to pause your code on that line until the promise fulfills, then return the resulting value.

async function hello() {
return greeting = await Promise.resolve("Hello");
};
hello().then(alert);

let’s see how to use this async and await in our code example.

const logInUserFunc = (username, password) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("function api response back");
if (Math.random() < 0.9999) {
resolve({ name: username });
} else {
reject(new Error("some error logInUserFunc"));
}
}, 1000);
});
};
const movieDetailFunc = (username) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("function movieDetail api response");
if (username === "miraj") {
resolve({ desc: "movie description" });
} else {
reject(new Error("some error movieDetailFunc"));
}
}, 1000);
});
};
const myAsyncFunc = async () => {
const user = await logInUserFunc("miraj", "123#");
const movieDesc = await movieDetailFunc(user.name);
console.log(movieDesc);
};
myAsyncFunc();
The output

as you can see in the above code, we have achieved the same execution behavior as Promise.then and Promise.catch approach in a way maintaining synchronous coding style with the help of async and await keywords.

Well, now we have gone through all the basics of the callback, promise, async, await in Javascript. Now you can decide which approach to use in your code when it needed asynchronous coding in Javascript.

Thanks For Reading, Follow me for more. If you do have any questions please leave a comment. I will surely answer your questions. If you find this useful for other developers please like and share.

--

--

Miraj Hamid

Software Engineer with experience in analysis & design in Java ,Spring-boot, NodeJs & ReactJS | https://www.linkedin.com/in/mirajhamid/