Sysleaf

What is ES8 async-await & why it's better than Promise

. .
What is ES8 async-await & why it's better than Promise

Introduction

If you know promise in Javascript, then you should know async-await too, because it is much better than Promise . We know that writing async code that is readable, maintainable and debuggable, has always been challenging. Thanks to async-await, it is a new way to write asynchronous code that looks almost like a synchronous code.

Async-await is fundamentally built on top of promise, So you must have prior knowledge of promise. If not, you can read about the promise in this article.

At the time of writing, async-await is not yet the part of ECMAScript standard, but since it has reached stage 4(finished proposal) of the ES8 draft, there are more chances that it will be the part of ES8(ES2017) final release.

Even if its not part of standard yet, async-awiat is supported in and above Chrome55, Firefox52, Edge, Safari10.1, and NodeJS8. But if you are are targeting browser that does not support async-await, then you need to either transpile or polyfill your async-await code using Babel.

To demonstrate the point in a concise way, you might see only the partial code in between the article. In the 'reference section', you can get all the links to complete source code, live demo and external references, if any.

Syntax: Promisify

Why are we discussing ‘promisify’ in an async-await article? Because async-await is just syntactic sugar over promise. So the original problem “how to promisify a function” is still the problem: earlier we used to solve it using promise’s syntax; now we will solve it using async-await syntax. In fact whatever problem we solved using promise in this article , we will now solve those problems using async-await in this article.

Let’s take an example: Suppose we want to fetch user profile after successful login. We want to do all these asynchronously(non-blocking way); To do this, we will first get the login token asynchronously using function getLoginToken and use that login token to fetch user profile asynchronously using function fetchUserProfile. Assume, getLoginToken and fetchUserProfile function is doing its work asynchronously and returning promise.

How To Promisify Function?

See below code on how to promisify function using async-await and promise syntax. Notice how we are using keyword async and await.

  • async function getUserProfile() {
      let token = await getLoginToken("username", "password")
      if(token){
        let usrData = await fetchUserProfile(token);
        usrData? return usrData: throw new Error("could not fetch userProfile");
      }else{
        throw new Error("Invalid username/password");
      }
    }
    
  • function getUserProfile(){
      return new Promise(function(fulfill, reject){
        getLoginToken("username", "password")
          .then( (token) => {
            if(token){
              fetchUserProfile(token)
                .then( (usrData) => {
                  usrData? fulfill(usrData): reject("could not fetch userProfile");
                })
            }else{
              reject("Invalid username/password");
            }
          })
      });
    }
    

When we put async keyword before function, it makes that function to behave more like promisified function without using Promise’s construct. See below how async constructed function behave like promisified function.

  1. Writing async keyword before function makes that function to return promise.
  2. return statement inside async constructed function makes that function to resolve promise with a return value.
  3. throw statement or any unexpected error inside async constructed function makes that function to reject promise with an error.

Putting await keyword before function tells that function: Hey! please pass me the data when you resolve your promise. You can think of await like a then method of promise. See some important point on the usage of await.

  1. await keyword can be used only inside an async constructed function and it should be prefixed only before those function that does an async operation and return promise.
  2. Using await keyword before synchronous function or function that does async opration but does not return promise, might give unexpected result.
  3. await keyword can be used before constant value like var x = await 2, but it does not make sense to do so and will have no impact at all.

How To Use Promisified Function?

We have promisified our function getUserProfile with async keyword in above section. Now we will see how to use it. There are 2 ways to use async constructed promisified function: one is using await keyword and another is using then method. See below code on how to do this.

wait! in 1st method, why we are using async constructed IIFE? Because we know, from above section, that await keyword can only be used inside async prefixed function.

wait! wait! in 2nd method, how can we use then method? is it legitimate? Remember! function getUserProfile is prefixed with async keyword and from above section, we know that any async prefixed function is promisfied function, which return promise. So it is legitimate to use then method here.

  • (async function() {
      let usrProfile = await getUserProfile();
      console.log(usrProfile);
    })()
    
    //OR
    
    getUserProfile()
      .then( (usrProfile) => {
        console.log(usrProfile) 
      })
    
  • getUserProfile()
      .then( (usrProfile) => {
        console.log(usrProfile) 
      })
    

Limitation: important

So far we saw, how async-await can solve the problem that promise can. But can async-await directly/independently solve these problems without the help of promise? No. See below to know more.

We know that promise can even promisify the function which has event based logic. But can we do the same using ‘async-await’? Not at all. For example, can you promisify below function getMyFavouriteDigit directly using async-await? No.

function getMyFavouriteDigit() {
  setTimeout(() => {
    var randomDigit = Math.floor(Math.random()*9);
    if (randomDigit === 6) {
      return "i got my favourite digit";
    } else {
      throw new Error("i could not got my favourite digit");
    }
  }, 1000);
}

//can be promisified as below using promise syntax

function getMyFavouriteDigit() {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      var randomDigit = Math.floor(Math.random() * 9);
      if (randomDigit === 6) {
        resolve("i got my favourite digit");
      } else {
        reject("i could not got my favourite digit");
      }
    }, 1000);
  });
}

We also know that ‘promise’ can directly replace ‘callback system’ but can ‘async-await’ directly replace callback. No, Not at all.

The reason for all above is the await keyword: main clause/term to use ‘await’ keyword is that await keyword can only be used before those function that returns a promise and in above example(‘event based’ & callback), there is no concept of promise. So it simply means: we can’t use async-await feature directly or independent of promise.

async-await can not even be used, directly/independently, to promisify normal asynchronous function, if all the API used inside that function are not inbuilt promised based API.

Chaining Promises

We know how we use promise chain for interdependent asynchronous function. Now we will see how to implement the same using async-await. In promise, we used to make chain of then method, but here, we will just put await keyword before function call.

See the requirement tab: requirement is pretty basic, funcGetText2 should be called only after funcGetText1 is finished as ‘funcGetText2 ’ is dependent on the result text returned by ‘funcGetText1’. Simillarly, funcGetText3 is dependent on funcGetText2 and printFinalText is dependent on funcGetText3.

  • //Suppose below pseudo code is requirement
    //funcGetText1, funcGetText2, funcGetText3 are async
    text1 = funcGetText1();
    text2 = funcGetText2(text1);
    text3 = funcGetText3(text2);
    printFinalText(text3)
    
  • async function getText(){
      let text1 = await funcGetText1();
      let text2 = await funcGetText2(text1);
      let text3 = await funcGetText3(text2);
      printFinalText(text3)
    }
    getText();
    
  • funcGetText1()
      .then(funcGetText2)
      .then(funcGetText3)
      .then(printFinalText)
    

Array Of Promises

Let’s take an example, Where we want to fetch four URLs at the same time. Fetching of one URL is not dependent on another URL, so it has to be done parallelly. We also want to print the result of each URL fetch only when all of the URLs have been fetched. See below code on how to do this.

  • var promiseArray = [];
    var urls = ['url1', 'url2', 'url3', 'url4'];
    
    for(var i=0; i<; i++){
      promiseArray.push(fetch(urls[i]));
    }
    
    function printResult(result){
      console.log("all urls has been fetched");
      for(var i=0; i<result.length; i++){
        console.log("result for url ", i, " is ", result[i]);
      }
    }
    
    async function fetchAllURLs(){
      //see here, how to await array of promise
      var result = await Promise.all(promiseArray);
      //below is incorrect way to await on array of promise
      //var result = await promiseArray;
      printResult(result);
    }
    
    fetchAllURLs();
    
  • var promiseArray = [];
    var urls = ['url1', 'url2', 'url3', 'url4'];
    
    for(var i=0; i<; i++){
      promiseArray.push(fetch(urls[i]));
    }
    
    Promise
      .all(promisArray)
      .then(printResult)
    
    function printResult(result){
      console.log("all urls has been fetched");
      for(var i=0; i<result.length; i++){
        console.log("result for url ", i, " is ", result[i]);
      }
    }
    

If you notice, we are using promise.all() in async-await version because there is no simmilar syntax in `async-await’ to promisify array of promise.

Please note one difference on the usage of await as shown in below code: one is non-parallel; another is parallel. See how we are using the ‘parallel’ version snippet in above code.

Putting await before fetch in this way, will make sure that next URL fetch start only when the previous one has finished. This is not what we wanted, this is just too sequential. What we wanted was parallel fetch, not the sequential one.

for(var i=0; i<; i++){
  //parallel fetch
  promiseArray.push(fetch(urls[i]));
}

//Both looks same, but works quite different

for(var i=0; i<; i++){
  // non-parallel fetch
  promiseArray.push(await fetch(urls[i]));
}

Error Handling

Any error in a programme must be handled otherwise application can crash and go to inconsistent state. We will see the flow of error handling in async constructed function. Error handling in async function is just like normal try catch. See below code for an example.

In below code, errorHandler1 will be called when any of funcGetText1, funcGetText2 and funcGetText3 reject its promise or throw error. While errorHandler2 will be called when any of errorHandler1, funcGetText4 and printFinalText reject its promise or throw error.

  • async function getText(){
      try{
        try{
          await funcGetText1();
          await funcGetText2();
          await funcGetText3();
        } catch(e) {
          errorHandler1(e);
        }
        await funcGetText4();
        await printFinalText();
      } catch(e) {
        errorHanlder2(e);
      }
    }
    
    getText();
    
  • funcGetText1()
      .then(funcGetText2)
      .then(funcGetText3)
      .catch(errorHandler1)
      .then(funcGetText4)
      .then(printFinalText)
      .catch(errorHanlder2)
    

Reference

  1. See basic of async-await on google web fundamentals
  2. See async api on MDN
  3. See await api on MDN
  4. See the current status of ECMA-262