Callbacks, Promises, and Async-Await

Puja Chaudhury
6 min readJul 26, 2021

In my last article, I talked about Synchronous and Asynchronous functions and we encountered an issue with Asynchronous functions where the compiler returns an ‘undefined’ output when the async function is called before its execution is completed. To solve that issue we have three methods of solution:

  1. Callbacks
  2. Promises
  3. Async-Await

In the next sections, we will talk about these methods in detail with examples.

Callbacks

A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action. Callbacks are often used to continue code execution after an asynchronous operation has been completed — these are called asynchronous callbacks.

Source: Cloudinary

Let’s look at some code again.

console.log("Hello,"); //Instruction 1const asyncExample = (givenName) => {setTimeout(() => {return { name: givenName };}, 3000);};console.log(asyncExample("I am Puja")); //Instruction 2console.log("How are you?"); //Instruction 3

For the asynchronous function above, we get an undefined output.

Hello,
undefined
How are you?

However, if we use a callback function, this problem can be resolved.

console.log("Hello,"); //Instruction 1const asyncExample = (givenName,callback) => {setTimeout(() => {callback( { name: givenName });}, 3000);};const names=asyncExample("I am Puja",names=>{
console.log(names)
}); //Instruction 2
console.log("How are you?"); //Instruction 3

Upon executing the above code snippet, we get the output of the async function only when the setTimeout function is done running.

Hello,
How are you?
I am Puja

We pass the callback function in the asyncExample function as a parameter and we wrap the return value of the asyncExample function in the callback function. As soon as the setTimeout function is done running, the callback function is called and everything inside that function is executed.

The Callback Hell

Source: Thapa Technical

Callback Hell, also known as Pyramid of Doom, is an anti-pattern seen in code of asynchronous programming. A Callback is a function “A” that is passed to another function “B” as a parameter. The function “B” executes the code “A” at some point. The invocation of “A” can be immediate, as in a synchronous callback, or, it can occur later as in an asynchronous callback.

Let's see an example. The function below returns the first movie of the movie list i.e. ‘Arrival’.

const nameInfo = (firstName, secondName, callback) => {setTimeout(() => {callback({ fname: firstName, sname: secondName });}, 3000);};const hobbiesInfo = (firstName, callback) => {setTimeout(() => {callback(["Cooking", "Swimming", "Movies"]);}, 3000);};const favMovies = (hobby, callback) => {setTimeout(() => {callback(["Arrival", "Interstellar", "Shutter Island"]);}, 3000);};const names = nameInfo("Puja", "Chaudhury", (names) => {hobbiesInfo(names.fname, (hobbies) => {favMovies(hobby[0], (movies) => {console.log(movies[0]);});});});

As evident, when more than one nested async functions are involved, the callbacks become loopy within functions and the code becomes unreadable and just messy. Thankfully there are two more methods that can help us pull out of this call-back hell!

Promises

Source: Freecodecamp

Promises- probably one of the most feared topics in javascript by newbie devs. I know it was mine! But I ‘promise’ to help you out with all my might. Alright, alright, I think it's obvious that some puns are coming along the way for this section (hehe). So what are promises? Promises are used to deal with async functions while avoiding every coder’s nightmare- the callback hell. A promise can be either in a fulfilled, rejected, or pending state. You’d be returned with either a resolved value or a reason for failure. The moment you call the promise constructor, it will get to work right away.

Let me show you how it works in action.

const promise = new Promise((resolve, reject) => {setTimeout(() => {resolve({ fname: "Puja" });}, 2000);});promise.then((name) => {console.log(name.fname);});

The output of this will be:

Puja

We can use the ‘reject’ keyword to throw an error when there is any sort of problem in running the async function. Now let's refactor our callback hell code and fill it with sweet promises!

const nameInfo = (firstName, secondName) => {return new Promise((resolve, reject) => {setTimeout(() => {resolve({ fname: firstName, sname: secondName });}, 3000);});};const hobbiesInfo = (firstName) => {return new Promise((resolve, reject) => {setTimeout(() => {resolve(["Cooking", "Swimming", "Movies"]);}, 3000);});};
const favMovies = (hobby) => {return new Promise((resolve, reject) => {setTimeout(() => {resolve(["Arrival", "Interstellar", "Shutter Island"]);}, 3000);});};nameInfo("Puja", "Chaudhury").then((names) => hobbiesInfo(names)).then((hobbies) => hobbiesInfo(hobbies)).then((movies) => favMovies(movies)).then((movie) => console.log(movie[0]));

The code with promises is way cleaner, simpler, and nicer and as I promised, it is gonna save you from hell! If we use promise.all() we can get the output of two async functions simultaneously as well. Pretty neat right?

Promise.all(async1,async2)
.then(result=>console.log(result));

I hope you are with me on this tutorial so far because what’s coming next is gonna make your code much more readable and clean.

Async-Await

Source: Frontend Weekly

As developers, we are mostly used to writing Synchronous code i.e. one command after the other. Callbacks and promises can throw us off a little bit because of their loopiness. Async- await solves that exact problem. Async-await is basically syntactic sugar for promises as it still uses promises in the background to fetch the data.

Let's refactor our code using async-await.

const nameInfo = (firstName, secondName) => {return new Promise((resolve, reject) => {setTimeout(() => {resolve({ fname: firstName, sname: secondName });}, 3000);});};const hobbiesInfo = (firstName) => {return new Promise((resolve, reject) => {setTimeout(() => {resolve(["Cooking", "Swimming", "Movies"]);}, 3000);});};const favMovies = (hobby) => {return new Promise((resolve, reject) => {setTimeout(() => {resolve(["Arrival", "Interstellar", "Shutter Island"]);}, 3000);});};
//Syncasync function userInfo() {const names = await nameInfo("Puja", "Chaudhury");const hobbies = await hobbiesInfo(names.fname);const movies = await favMovies(hobbies[0]);console.log(movies[0]);}userInfo();

All we did here is let javascript know that we are expecting an async function return by putting the ‘async’ keyword in front of the userInfo function and put the await keyword in front of our function calls. It's really that easy.

Thanks a lot for reading this piece, it was longer than I had originally planned but it's jampacked with all the necessary info that you need to get your basics clear! I am Puja, a developer in the making. It takes a lot of work to research for and write such an article, and a clap or a follow 👏 from you means the entire world 🌍to me. It takes less than 10 seconds for you, and it helps me with reach! You can also ask me any questions, or point out anything, or just drop a “Hey” 👇 down there.

--

--

Puja Chaudhury

Masters' student in Robotics focusing on machine learning. Experienced in AI, NLP, and CV through internships, research projects, & published papers.