DEV Community

Cover image for Yet another intro to Promise, async - await in JavaScript
Asif Mohammad
Asif Mohammad

Posted on

Yet another intro to Promise, async - await in JavaScript

Heads Up

  • I am assuming you know basic JavaScript (>= ES6)
  • This post is rather semantic than syntactic
  • I have put up links for further explanations wherever I thought would be necessary.

Let's Go

Examples are the best way to make someone understand something because it's easier to correlate with something practical than digest theory - Asif Mohammad

For example, every time we search for the meaning of a word online we tend to read its examples to make more sense.

See what I did there? 😉

So lets just consider an example of baking and eating some delicious cake. We can break down the whole process into three basic steps

  1. Baking Cake
  2. Serving Cake
  3. Eating Cake

The Javascript equivalent of this process could be portrayed as the following functions

const bakeCake = () => console.log('Cake is baked');
const serveCake = () => console.log('Cake is served');
const eatCake = () => console.log('Cake eaten');

Yes, it is neither a proper equivalence nor I am eligible to be a good cook but it serves the purpose if not the cake.😉

Our cake baking journey would go something like

bakeCake(); // Cake is baked
serveCake(); // Cake is served
eatCake(); // Cake is eaten

But most real world scenarios like baking cake and scenarios on the web like fetching user posts, have something in common, they take time

Lets adjust our functions so that they reflect such and lets consider each of our step takes 2 seconds of time

const bakeCake = () => {
  setTimeout(()=>{
    console.log('Cake is baked')
  }, 2000);
};
const serveCake = () => {
  setTimeout(()=>{
    console.log('Cake is served')
  }, 2000);
};
const eatCake = () => {
  setTimeout(()=>{
    console.log('Cake is eaten')
  }, 2000);
};

We cannot call these three functions sequentially because they will not run synchronously. Why?

So we should follow the standard callback pattern which is being used for a long time now.

Using Callback Functions

const bakeCake = (cbkFn) => {
  setTimeout(()=>{
    console.log('Cake is baked');
    cbkFn();
  }, 2000);
};

const serveCake = (cbkFn) => {
  setTimeout(()=>{
    console.log('Cake is served');
    cbkFn();
  }, 2000);
};

const eatCake = () => {
  setTimeout(()=>{
    console.log('Cake is eaten')
  }, 2000);
};

bakeCake(()=>{
  serveCake(()=>{
    eatCake();
  });
});

Understanding the callback pattern

When we use callbacks we expect the function we pass to be called back when required (hence the name callback functions). The problem with callbacks is the often occurring Callback Hell.

Consider our cake baking, when the steps are extended it becomes

bakeCake(() => {
  decorateCake(() => {
    tasteCake(() => {
      cutCake(() => {
        serveCake(() => {
          eatCake(() => {

          });
        });
      });
    });
  });
});

This is what we call as the Callback Hell. The more things you are willing to do in this process the more complex and messy it will get. It works, It's fine but we always want something batter better.

Promise

Promise as the name goes is a pattern, rather than being an object/function, where you are promised the execution of a piece of code and it enables you to code further based on your trust on that promise. JS Engine is a machine so you can always trust when it promises you, unlike us evil Humans.

Rewriting our example using promises.
Lets skip serving the cake (yes we are the wild ones who eat directly off the stove)

const bakeCake = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Cake is baked');
      resolve();
    }, 2000);
  });
};

const eatCake = () => {
  setTimeout(() => {
    console.log('Cake is eaten');
  }, 2000);
};

bakeCake().then(eatCake);

What we did here is instead of executing the bakeCake function normally, we are enclosing it in a Promised environment. Previously we did not return anything in bakeCake but now we are returning a Promise to the callee.

A promise that the piece of code enclosed is executed with an assurance that once its completed, either successfully or broke down due to some abnormality, you will be notified.

resolve being the indicator of success and
reject for any abnormal execution (mostly for an error)

In our case of bakeCake we are resolving the promise ( notifying the callee that the piece of code which was promised to be supervised has completed successfully) and on the callee's side we can listen to the notification with then and the abnormalities with catch which we haven't covered here.

Promises enable chaining which is not possible by callbacks.
Suppose we had to log our cake baking. We could chain our functions as

const bakeCake = (cakeLog) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Cake is baked');
      cakeLog.push('Perfectly baked!')
      resolve(cakeLog);
    }, 2000);
  });
};

const serveCake = (cakeLog) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Cake is served');
      cakeLog.push('Served Well');
      resolve(cakeLog);
    }, 2000);
  });
};

const eatCake = (cakeLog) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Cake is eaten');
      cakeLog.push('Ate like its the last cake on earth')
      resolve(cakeLog);
    }, 2000);
  });
};

bakeCake([])
  .then(serveCake)
  .then(eatCake)
  .then(console.log);

We pass in an empty array [] to bakeCake and when it resolves it pushes its own log statement into the array then reaches the first then when resolved and the function you pass as the parameter to then gets the parameter as the content you passed into the resolve call.

To understand better. We can rewrite the function calls as

let cakeLog = [];
bakeCake(cakeLog).then(cakeLog => {
  serveCake(cakeLog).then(cakeLog => {
    eatCake(cakeLog).then(cakeLog => {
      console.log(cakeLog);
    });
  });
});

We pass cakeLog into bakeCake and we get it back (after getting updated in the cakeLog) as a parameter to the function we pass in to the then call. So we can pass it along to serveCake and repeat the same till we need to consume the accumulated/gross data.

It makes more sense when we correlate to a actual scenario like

let userID = 1001;
getUser(userID)
    .then((user) => getPosts(user))
    .then((posts) => getTotalLikes(posts))
    .then((likeCount) => console.log(likeCount));

But We always want better.

async - await

async - await enable us to write asynchronous code just like how we would write synchronous code by acting as a syntactical sugar to the powerful Promise pattern.

A blueprint of using async await with respect to the underlying Promise pattern would be

async function(){
  let paramYouSendIntoResolve = await promReturningFn(); 
}
  1. Call your asynchronous function but use an await keyword before it
  2. Instead of passing in a function to capture the resolved data. Take it as a return value of the function. Wow
  3. Just one minor discomfort. As you are doing asynchronous stuff amidst of ever synchronous JS flow. Just append async to the function where you use await so that JS Engine knows you are going to do async stuff and interprets accordingly because it has to turn them into Promises later.

Back to our cake baking. (excluded the logging stuff)

const bakeCake = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Cake is baked');
      resolve();
    }, 2000);
  });
};

const serveCake = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Cake is served');
      resolve();
    }, 2000);
  });
};

const eatCake = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Cake is eaten'); 
      resolve();
    }, 2000);
  });
};

(async ()=>{
  await bakeCake();
  await serveCake();
  await eatCake();
})();

Notice that we have used an IIFE here to force async function execution.

There we are!
We have reached the ability to call asynchronous functions Asif as if they were synchronous.

Thanks for reading. I hope you got something out of this

Top comments (1)

Collapse
 
aftabksyed profile image
Aftab Syed

Very nice and well-written article!