JavaScript for Testers - Part 2 (Callbacks, Promises and async-await)
Tags: javascript, testing, beginners, asynchronous
Published: 2023-10-15 19:07:49

Namaskaar 🙏 🙂

This is a two part tutorial on getting up and running with JavaScript, for the testers, so that they can leverage this knowledge while writing test automation scripts.

The way this tutorial has been planned is

* We begin with covering JavaScript Basics, especially ES6 (that’s [part 1](https://dev.to/asheeshmisra/javascript-for-testers-1dpj))
* Then we deal with Asynchronous JS (that’s this tutorial)

This is **a beginner friendly tutorial for the testers by a tester**.

In this part of the tutorial, we focus on Asynchronous JavaScript. To understand the concepts myself, I referred to MDN Docs, multiple videos on [Youtube](https://www.youtube.com), [Udemy](https://www.udemy.com) and [Linkedin learning](https://www.linkedin.com/learning/). I would like to make special mention of [Colt Steele](https://www.coltsteele.com) and his Udemy course, [The Web Developer Bootcamp](https://www.udemy.com/course/the-web-developer-bootcamp). Colt is one of the best out there and his this course is like a gold mine for any person who wants to learns Web Development. Some of the examples used in this tutorial draw inspiration from Colt's course. 

 ---

>If you feel lost or confused at any time while reading through this part of tutorial, don't feel bad. This part is indeed confusing. Most of the experts recommend doing it multiple times (reading the stuff and practicing) and it will start making sense. 👼

---

## Introduction

JavaScript is a `synchronous`, `single threaded` and `blocking` language. **_`Synchronous`_** means that only one operation can be in progress at a time. **_`Blocking`_** means that no matter how long a previous process takes, the subsequent process won't start until former is completed. A **_`Thread`_** is simply a process that our JavaScript program can use to run a task. Each thread can do only one task at a time. JavaScript has just one thread called the *main thread* for executing any code.

In simple words this means that we can have only one operation in progress at a time.

Consider the following example

```javascript
const founders = () => console.info("Batman founded the Justice League with Superman and Wonder Woman");
const team = () => console.info("Aquaman, Green Lantern, Shazam, Flash, Cyborg joined Justice League");
const war = () => console.info("Justice League fought the war with Darkseid and saved earth from his wrath");

founders();
team();
war();

/*
Output:
Batman founded the Justice League with Superman and Wonder  Woman
Aquaman, Green Lantern, Shazam, Flash, Cyborg joined Justice League
Justice League fought a war with Darkseid and saved earth from his wrath
*/
```
<br>First we invoke the *founders()* function which gets added to the `callstack`. It returns a value and then gets popped off the `callstack`. Then we invoke *team()* function which gets pushed to the `callstack`, it returns a value and get popped out of the `callstack`. The same happens to the *war()* function.

Now, imagine that we had some function that fetched data from an API. We don't really know how long is it going to take. Let's assume that it takes 5 seconds for the server to respond. This would mean that the user will have to wait for 5 seconds with an unresponsive website. We would not like that to be the case and therefore, would want to make the function asynchronous in order to prevent the function from blocking the rest of our code.

**Asynchronous** means that the function(s) is running in the background, performing its tasks, while the other part of our code can still keep running. We should think of asynchronous tasks as *tasks that can start now and finish their execution later*.

To implement asynchronous code, JavaScript alone is not enough. We need new pieces which are outside the JavaScript to help us write asynchronous code an which is where Web Browsers come into play. Web Browsers define functions and APIs that allow us to register functions that should NOT be executed synchronously, and should instead be invoked asynchronously when some kind of event occurs.

Event, for example, could be

* Passage of time (`setTimeOut` or `setInterval`), OR
* User's interaction with the mouse (`addEventListener`), OR
* Arrival of data over the network (`callbacks`, `promises` and `async`-`await`)

We can make our code do several things at the same time instead of waiting for main thread to finish.

## Common Patterns of Asynchronous Programming

3 most common patterns of asynchronous programming in JavaScript are:

* *Callbacks*
* *Promises*
* *async* - *await*


![Common Patterns of Asynchronous Programming in JavaScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l468glz3i7kq7u324ekg.jpg)


Let's learn about them one by one.


---


## Callbacks

**What are they?** In JavaScript, functions are first-class objects. That is, we can work with them in the same way we work with other objects, **like assigning them to variables and passing them as arguments into other functions**.

>_A programming language is said to have [First-class functions](https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function) when functions in that language are treated like any other variable. For example, in such a language, a function can be passed as an argument to other functions, can be returned by another function and can be assigned as a value to a variable. ([MDN Docs](https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function))_

A `callback` function is a function that is passed as an argument to another function, to be *'called back'* at a later time. That is, it is then invoked inside the outer function to complete some kind of routine or action.

Consider the following example. It shows a *synchronous callback*.

```javascript
//Old Flash (somewhere in 1940s or 50s) was 'Jay Garrick'
const realNameOfDcHero= (name) =>{
    let realName = "Jay Garrick";
    if (name === "Superman")
        realName = "Clark Kent";
    else if (name == "Batman")
        realName = "Bruce Wayne";
    else if (name == "Wonder Woman")
        realName = "Diana";
    else if (name == "Flash")
        realName = "Barry Allen";
    console.log(`Real name of ${name} is ${realName}`);
}

const getHeroName= (whatsTheName, callback) =>{
    console.log("Getting the Hero's Name. Sit Tight.");
    callback(whatsTheName);
}

console.info("Start");
getHeroName("Wonder Woman", realNameOfDcHero);
console.info("End");

/*
Output:
Start
Getting the Hero's Name. Sit Tight.
Real name of Wonder Woman is Diana
End
*/
```
<br>**NOTE:** We need not specify the *realNameOfDcHero* in the *getHeroName* function's definition. We could have passed anything, like *heroName* or *callback* or *cb* etc. 

Before we see the *asynchronous callback* example, we need to understand `setTimeout()` function. The `setTimeout` is a JavaScript function that takes two  parameters. The first parameter is another function, and the second is  the time, in milliseconds, after which that function (passed as first parameter) should be executed.

Ok, now let's dive into the example,

```javascript
//Example - Asynchronous Callback
const realNameOfDcHero= (name) =>{
    let realName = "Jay Garrick";
    if (name === "Superman")
        realName = "Clark Kent";
    else if (name == "Batman")
        realName = "Bruce Wayne";
    else if (name == "Wonder Woman")
        realName = "Diana";
    else if (name == "Flash")
        realName = "Barry Allen";
    console.log(`Real name of ${name} is ${realName}`);
}

const getHeroName= (whatsTheName, realNameOfDcHero) =>{
    console.log("Getting the Hero's Name. Sit Tight.");
    setTimeout(()=>{
        realNameOfDcHero(whatsTheName);
    },2000);
}

console.info("Start");
getHeroName("Flash", realNameOfDcHero);
console.info("End");
```
<br>In the example above, we have used the `setTimeout` function which would execute the callback function after a delay of 2000 milliseconds (or 2 secs). This results in following output:


![Example of Asynchronous Callback](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a2wt95lfvigcdvjyvph3.gif)

```javascript
/*
Output:
Start
Getting the Hero's Name. Sit Tight.
End
Real name of Flash is Barry Allen
*/
```

<br>As we can see the statement in the example above executed asynchronously. So now, we know how to simulate asynchronous behaviour in JS. 

Let's see another example. For this example, we will need a web page and a .css file also apart from the .js file. Therefore, we will be creating following 3 files

```html
<!--index.html-->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Learning Async JS - Callbacks</title>
    <link rel="stylesheet" href="css/style.css"/>
</head>
<body>
    <button>Click Me!</button>
    <script src="js/asyncJs.js"></script>
</body>
</html>
```

```css
/* css/style.css */
button{
    background-color:aqua;
}

.secondaryColor{
    background-color: coral;
    font-family:'Segoe UI';
}
```

```javascript
// js/asyncJs.js
const btn = document.querySelector('button');

function toggle(){
    btn.classList.toggle('secondaryColor');
}

toggle();
```

<br>When we finish creating these files in the order as shown above and save changes and we load the *html* file in browser, we observe that the colour of button changes immediately. In other words, the *toggle()* is called instantaneously as soon as the page (re)loads.

What if we don't want this? What if we want it to be called when the button is clicked?

To accomplish this we will need to add an `eventlistener` and add a callback to it. How?

```javascript
// js/asyncJs.js
const button = document.querySelector('button');

function toggle(){
    button.classList.toggle('secondaryColor');
}

//toggle();   

button.addEventListener('click', toggle);
```
<br>We have used toggle() function as a callback function in the addEventListener() on button . 

By the way, the above js code is same as this 😄

```javascript
//we have used anonymous function
const button = document.querySelector('button');

button.addEventListener('click', function (){
    button.classList.toggle('secondaryColor');
});
```
<br>and this 😃

```javascript
// we have used arrow function
const button = document.querySelector('button');
button.addEventListener('click', () =>{
    button.classList.toggle('secondaryColor');
});
```
<br>
## Nested Callbacks
Consider we want to see the rainbow colors displayed on our web page in the order : VIBGYOR.

To do so, we write the following code in our JS file

```javascript
document.body.style.backgroundColor = "violet";
document.body.style.backgroundColor = "indigo";
document.body.style.backgroundColor = "blue";
document.body.style.backgroundColor = "green";
document.body.style.backgroundColor = "yellow";
document.body.style.backgroundColor = "orange";
document.body.style.backgroundColor = "red";
```
<br>When we reload our web page, we see "red" color only. The script ran so fast that the background colour of web page changed to red before we could even blink.

Let's try to solve this problem by using asynchronous callbacks. We will start small. Delete the above code from the JS file, paste the code below, save changes and refresh the web page.

```javascript
setTimeout(()=>{
    document.body.style.backgroundColor = "orange";
},1000);

setTimeout(()=>{
    document.body.style.backgroundColor = "red";
},2000);
```
<br>Ok, it worked 🙌. We saw orange first and then after a second we saw red.

Let's scale it up.

```javascript
setTimeout(()=>{
    document.body.style.backgroundColor = "violet";
},1000);

setTimeout(()=>{
    document.body.style.backgroundColor = "indigo";
},2000);

setTimeout(()=>{
    document.body.style.backgroundColor = "blue";
},3000);

setTimeout(()=>{
    document.body.style.backgroundColor = "green";
},4000);

setTimeout(()=>{
    document.body.style.backgroundColor = "yellow";
},5000);

setTimeout(()=>{
    document.body.style.backgroundColor = "orange";
},6000);

setTimeout(()=>{
    document.body.style.backgroundColor = "red";
},7000);
```
<br>When we save the above change to our JS file and reload web page, we see the change in color happen gradually.

However, there is a problem in the above example 😟. We need to keep track of how many milliseconds do we need to wait before displaying a specific color. We can get rid of keeping track of this by nesting the callbacks, like so. Again, starting small

```javascript
setTimeout(() => {
    document.body.style.backgroundColor = "violet";
    setTimeout(() => {
        document.body.style.backgroundColor = "indigo";
    }, 1000);
}, 1000);
```
<br>This works!, let's paint the whole rainbow now 🌈.

```javascript
setTimeout(() => {
    document.body.style.backgroundColor = "violet";
    setTimeout(() => {
        document.body.style.backgroundColor = "indigo";
        setTimeout(() => {
            document.body.style.backgroundColor = "blue";
            setTimeout(() => {
                document.body.style.backgroundColor = "green";
                setTimeout(() => {
                    document.body.style.backgroundColor = "yellow";
                    setTimeout(() => {
                        document.body.style.backgroundColor = "orange";
                        setTimeout(() => {
                            document.body.style.backgroundColor = "red";
                        }, 1000);
                    }, 1000);
                }, 1000);
            }, 1000);
        }, 1000);
    }, 1000);
}, 1000);
```
<br>Wow! this works. However, as we can see, we ended up writing something which is hard to read and difficult to maintain. Such a piece of code is referred to as ***'Callback Hell'*** 😵. 

Let's take one more example and then we will (re) solve the 'callback hell' problem, I 'promise' 😄. Consider making request(s) to a web API. We do not know how much time would it take for the response to come. We might want to do something based upon the response but we would need to wait for the response first. Also, it is not necessary that a response is returned (i.e., request is successful). Our request may timeout due to multiple reasons like  network congestion, server busy, site down, incorrect credentials etc.. (request failed). A common pattern (before promises arrived) to make API requests was to pass in two call backs, one would execute if request was successful and other if request failed; and then if the request was successful we may do more request which would result in nesting and soon the code would look like this:

```javascript
requestDataFromAPI(
    ()=>{
      //this would execute if successful
      requestDataFromAnotherAPI(
        ()=>{
            //this would execute if successful
            requestDataFromYetAnotherAPI(
                ()=>{
                    //this would execute if successful
                },
                
                () =>{
                    //this would execute if failed
                }
            );
          },
        () =>{
            //this would execute if failed
          }
        );  
    },
    () => {
    //this would execute if failed
    }
  );
```
<br>This is not only very ugly looking spaghetti code but difficult to understand and maintain also. Let's see a practical implementation:

```javascript
const requestDataFromAPI = (url, success, failure) => {
  const timeInterval = Math.floor(Math.random() * 3000) + 500;
  setTimeout(() => {
    if (timeInterval > 3000) {
      failure(`Couldn't get the data.`);
    } else {
      success(`Data Retrieved: ${url}`);
    }
  }, timeInterval);
};
```
<br>The function above fakes request to a fictional API based on DC Comics characters. It also takes in two callbacks and inside the function we check for a variable *timeInterval* and based upon its value, execute the appropriate callback. Now, let's use it

```javascript
requestDataFromAPI(
  "dccomics.com/trinity",
  () => {
    //this callback will execute when the request is successful
    console.log("DC Trinity: Superman, Wonder Woman, Batman");
    requestDataFromAPI(
      "dccomics.com/citizensOfThemyscira",
      () => {
        //this callback with execute when the request is successful
        console.log("Queen Hippolyta, Artemis, Cassie Wonder Girl, Donna Troy");
        requestDataFromAPI(
          "dccomics.com/greenlanterncorps",
          () => {
            console.log(
              "Members of Green Lantern Corps; John Stewart, Jessica Cruz, Hal Jordan, AbinSur, Alan Scott, Guy Gardener"
            );
          },
          () => {
            console.log(
              "Oh No!, Sinestro has brought the green lantern corps under his control of fear."
            );
          }
        );
      },
      () => {
        //this callback will execute when the request fails
        console.log("Alas!, Ares won and took over Themyscira");
      }
    );
  },
  () => {
    //this callback will execute when the request fails
    console.log("Trinity is gone, all hail Darkseid!.");
  }
);
```
<br>In the code above, we first make request to an endpoint of our fictional API *dccomics.com* to fetch members of Justice League, popularly called as the *'trinity'*. If the request is successful, we make request to a different endpoint and if that too is successful we make request to yet another endpoint. When we run this, this is what we see in the browser (the output is based upon random values and therefore is random, i.e. you may not get exactly the same output but it you run multiple times, you will see each of the possible combination).

![Nested Callback Example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b3uhbivxe7efcovd1q5h.jpg)

If the above image is not clear to you, I would encourage you to paste the code for *'requestDataFromAPI()'* definition and its usage in *async.js* file we created above, into your *js* file, save changes, refresh the web page and check the console tab in the browser.

In the code above we observe the nesting of callbacks and I am sure you would appreciate how difficult it is to understand and debug. 😶

ES 6 made a promise 😅 to get us out of this callback hell. Let's see what that is 😃

---

## Promise

As per [MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise),

>The **`Promise`** object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
>
>A `Promise` is a proxy for a value not necessarily known when the promise is created. It allows you to  associate handlers with an asynchronous action's eventual success value  or failure reason. This lets asynchronous methods return values like  synchronous methods: instead of immediately returning the final value,  the asynchronous method returns a promise to supply the value at some point in the future.
>
>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.

The lifecycle of a Promise is as follows:


![Lifecycle of a Promise](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zn9q5b88d5nu5ya8sk9h.jpg)

<br>To understand promise better let's use promise to write the above _requestDataFromAPI_ function and then make use of it using promises.

```javascript
const requestDataFromAPI = (url) => {
  return new Promise((resolve, reject) => {
    const timeInterval = Math.floor(Math.random() * 5000) + 500;
    setTimeout(() => {
      if (timeInterval > 3000) {
        reject(`Couldn't get the data.`);
      } else {
        resolve(`Data Retrieved: ${url}`);
      }
    }, timeInterval);
  });
};
```
<br>The function above takes in _url_ as a parameter. No callbacks here. Inside its body, the function returns a _Promise_, which however takes in two callbacks. One would be executed if the promise got resolved and other if it did not, in other words it got rejected.

Let's call this function in our browser's console and save the response to a variable to better understand. After a couple of rounds of execution, we observe, the promise is initially in `pending` state and after the `timeInterval` has elapsed, it resolves to either a `fulfilled` state or a `rejected` state.

![Request from API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2p0dvgfxm3gqkswiird3.png)

Now, let's convert the callback hell in code examples above (well… almost) into promises. 

For the rainbow example, let's first write the function to change the background colour of the webpage, in our JS file.

```javascript
const paintTheSky = (colour, timeInterval) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      document.body.style.backgroundColor = colour;
      resolve();
    }, timeInterval);
  });
};
```
<br>The function _paintTheSky_ takes two parameters the color and the `timeInterval`. Inside the function, we are creating a new `promise` which also takes in two parameters. First one is a `callback` which executes when the `promise` is resolved and the second is a `callback` which executes when the promise is rejected. They can have any names, not necessarily resolve and reject, but it's just that these names are intuitive and make our code readable. We are calling `setTimeout()`, which waits for 1 second, changes the color of web page and then resolves the promise.

Since _paintTheSky_ function is returning a promise, we can call `.then()` on it, like so

```javascript
paintTheSky("violet",1000)
  .then()
```
<br>and in this `then()` we can add a callback for _paintTheSky()_. 

```javascript
paintTheSky("violet",1000)
  .then(()=>{
      paintTheSky("indigo",1000);
});
```
<br>If we add a return to the _paintTheSky()_ called in the above fragment, we will effectively be returning a promise from `.then()`. So,

```javascript
paintTheSky("violet",1000)
  .then(()=>{
      return paintTheSky("indigo",1000);
});
```
<br>the code above is same as 

```javascript
paintTheSky("violet",1000)
  .then(()=> paintTheSky("indigo",1000)); /*using implicit return*/
```
<br>Since we want more colours of the rainbow to be printed, we can simply chain more `.then()` for the purpose, effectively achieving the same result as we did above, when we used callbacks only.

```javascript
const paintTheSky = (colour, timeInterval) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      document.body.style.backgroundColor = colour;
      resolve();
    }, timeInterval);
  });
};

paintTheSky("violet", 1000)
  .then(() => paintTheSky("indigo", 1000))
  .then(() => paintTheSky("blue", 1000))
  .then(() => paintTheSky("green", 1000))
  .then(() => paintTheSky("yellow", 1000))
  .then(() => paintTheSky("orange", 1000))
  .then(() => paintTheSky("red", 1000));
```
<br>In the above code we are calling _paintTheSky()_ and then since its returns a promise, we are using `.then()` to call the it again and then again and again. We can immediately see that not only have we flattened out the code structure, it has become very easy to read as well.

This was easy. Why? we didn't reject the promise, there was no reason to. However, there will be situations when we might need to reject it as well. In the DC callback hell example above, we made provisions for what to do if the (fake) API responded in time and otherwise.

To rewrite that example, using promises, we will first rewrite the _requestDataFromAPI()_, like so,

```javascript
const requestDataFromAPI = (url) => {
  return new Promise((resolve, reject) => {
    const timeInterval = Math.floor(Math.random() * 4000) + 500;
    setTimeout(() => {
      if (timeInterval > 3000) {
        reject(`Couldn't get the data.`);
      } else {
        resolve(`Data Retrieved: ${url}`);
      }
    }, timeInterval);
  });
};
```
<br>The _requestDataFromAPI()_ above takes in one parameter, the url (though we don't do anything with it, fake api request remember 😅). Inside the function, we are creating a new promise which takes in two parameters. First one is a callback which executes when the promise is resolved and the second is a callback which executes when the promise is rejected. Depending upon the if condition, that evaluates the comparison between `timeInterval` value and 3000, we either resolve the promise or reject it.

```javascript
const requestDataFromAPI = (url) => {
  return new Promise((resolve, reject) => {
    const timeInterval = Math.floor(Math.random() * 4000) + 500;
    setTimeout(() => {
      if (timeInterval > 3000) {
        reject(`Couldn't get the data.`);
      } else {
        resolve(`Data Retrieved: ${url}`);
      }
    }, timeInterval);
  });
};

requestDataFromAPI("dccomics.com/trinity") //returns a thenable object
  /* if promise gets resolved, then() is executed.*/  
  .then(() => {
    console.log("DC Trinity: Superman, Wonder Woman, Batman");
  })
  /* if  promise gets rejected, catch() is executed */
  .catch(() => {
    console.log("Trinity is gone, all hail Darkseid!.");
  });
```

<br>Since we have to either resolve or reject on each API endpoint call, we won't be able to chain the promises (we will see how can we chain the promises for this example and at what cost, a bit later). After adding code for the remaining two endpoints, the complete code looks like this

```javascript
const requestDataFromAPI = (url) => {
  return new Promise((resolve, reject) => {
    const timeInterval = Math.floor(Math.random() * 4000) + 500;
    setTimeout(() => {
      if (timeInterval > 3000) {
        reject(`Couldn't get the data.`);
      } else {
        resolve(`Data Retrieved: ${url}`);
      }
    }, timeInterval);
  });
};

requestDataFromAPI("dccomics.com/trinity") //returns a thenable object
  .then(() => {
    console.log("DC Trinity: Superman, Wonder Woman, Batman");
  })
  .catch(() => {
    console.log("Trinity is gone, all hail Darkseid!.");
  });

requestDataFromAPI("dccomics.com/citizensOfThemyscira")
  .then(() => {
    console.log(
      "Citizens of Themyscira: Queen Hippolyta, Artemis, Cassie Wonder Girl, Donna Troy"
    );
  })
  .catch(() => {
    console.log("Alas!, Ares won and took over Themyscira");
  });

requestDataFromAPI("dccomics.com//greenlanterncorps")
  .then(() => {
    console.log(
      "Members of Green Lantern Corps; John Stewart, Jessica Cruz, Hal Jordan, AbinSur, Alan Scott, Guy Gardener"
    );
  })
  .catch(() => {
    console.log(
      "Oh No!, Sinestro has brought the green lantern corps under his control of fear."
    );
  });
```
<br>and of course, we can further update it to this

```javascript
const requestDataFromAPI = (url) => {
  return new Promise((resolve, reject) => {
    const timeInterval = Math.floor(Math.random() * 4000) + 500;
    setTimeout(() => {
      if (timeInterval > 3000) {
        reject(`Couldn't get the data.`);
      } else {
        resolve(`Data Retrieved: ${url}`);
      }
    }, timeInterval);
  });
};

requestDataFromAPI("dccomics.com/trinity") 
  .then(() => console.log("DC Trinity: Superman, Wonder Woman, Batman"))
  .catch(() => console.log("Trinity is gone, all hail Darkseid!."));

requestDataFromAPI("dccomics.com/citizensOfThemyscira")
  .then(() => console.log("Citizens of Themyscira: Queen Hippolyta, Artemis, Cassie Wonder Girl, Donna Troy"))
  .catch(() => console.log("Alas!, Ares won and took over Themyscira"));

requestDataFromAPI("dccomics.com//greenlanterncorps")
  .then(() => console.log("Members of Green Lantern Corps; John Stewart, Jessica Cruz, Hal Jordan, AbinSur, Alan Scott, Guy Gardener"))
  .catch(() => console.log("Oh No!, Sinestro has brought the green lantern corps under his control of fear."));
```

<br>In the above code, we see how easy, clean and maintainable our code has become. 

From a QA's point of view, we would, for most of our time be dealing with standard libraries (and the `promises` returned by their methods) like [playwright](https://playwright.dev/), [supertest](https://www.npmjs.com/package/supertest), [jest](https://jestjs.io/) etc., and may never (or seldom) get to write ourselves something that creates and returns promises. 
Therefore, instead of focusing on  implementation of requestDataFromAPI(), we should be focusing on its usage. BUT then…, this is me, I leave the choice to you 😇

However, we can do the same stuff in a much neater way. Enter `async`-`await`  😇

---

## async await
We will again read the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) to learn what `async`and `await` are
>The `async` function declaration declares an `async` function where the await keyword is permitted within the function body. The `async` and `await`  keywords enable _asynchronous_, _promise-based_ behavior to be written in a  cleaner style, avoiding the need to explicitly configure promise  chains.

In simpler words, what this means is that `async` and `await` are essentially syntactical sugar wrapped around promises to make them look easier. All we need to do is to put all of our asynchronous code inside a function, an `async` function.  `async` functions allow us to write asynchronous code in a synchronous manner. How? 

The `async` keyword allows the function to return a _promise_ which would either get resolved or rejected, depending upon what we are doing inside that `async` function. That is, if the function returns a value, _promise_ is said to be resolved with the returned value and if it throws an exception then it is said to be rejected with the exception value. Now, inside the `async` function, we write asynchronous statements using the keyword `await`. This keyword pauses the execution of the `async` function and waits for the promise to be resolved.

Consider the rainbow example, we can transform the promise based implementation to `async`-`await`, like this:

```javascript
const paintTheSky = (colour, timeInterval) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      document.body.style.backgroundColor = colour;
      resolve();
    }, timeInterval);
  });
};

const paintTheRainbow = async function () {
  await paintTheSky("violet", 1000);
  await paintTheSky("indigo", 1000);
  await paintTheSky("blue", 1000);
  await paintTheSky("green", 1000);
  await paintTheSky("yellow", 1000);
  await paintTheSky("orange", 1000);
  await paintTheSky("red", 1000);
};
paintTheRainbow();
```
<br>In the example above, paintTheRainbow is an `async` function. Inside this function, we have multiple `await` statements in the order in which we want the colour transition to happen (same as in the callback and promise examples). The first await statement calls the _paintTheSky()_ function and waits for the promise returned by the function to be resolved. After that, the next `await` statement is executed and after its promise has been resolved, the next one and so on. 

We see that the same asynchronous code that we wrote using the callbacks (which had multi level nesting, thus the phrase 'callback hell') and the promises (which had atleast one level of nesting) has been written as if we are writing synchronous code. That's the beauty of `async`-`await`.
When we use JavaScript for automation (frontend/ backend), in later tutorials, we shall write almost all of our tests (and most of the helpers/ utility functions) using `async`-`await`.

We can convert the _promise_ version of DC example also into its corresponding `async`-`await` implementation, where we will observe that handling of promise rejection in case `async`- `await` is pretty similar to the way it was in the promise example above (`async`-`await` is just syntactic sugar on promises, remember 😅).

```javascript
const dcUniverse = async () => {
  try {
    await requestDataFromAPI("dccomics.com/trinity");
    console.log("DC Trinity: Superman, Wonder Woman, Batman");
  } catch (error) {
    console.log(`${error} Trinity is gone, all hail Darkseid!.`);
  }

  try {
    await requestDataFromAPI("dccomics.com/citizensOfThemyscira");
    console.log(
      "Citizens of Themyscira: Queen Hippolyta, Artemis, Cassie Wonder Girl, Donna Troy"
    );
  } catch (error) {
    console.log(`${error} Alas!, Ares won and took over Themyscira`);
  }

  try {
    await requestDataFromAPI("dccomics.com/greenlanterncorps");
    console.log(
      "Members of Green Lantern Corps; John Stewart, Jessica Cruz, Hal Jordan, AbinSur, Alan Scott, Guy Gardener"
    );
  } catch (error) {
    console.log(
      `${error} Oh No!, Sinestro has brought the green lantern corps under his control of fear.`
    );
  } finally {
    console.log("Heroes shall rise again.");
  }
};

dcUniverse();
```

<br>
##Outro
In this part of the tutorial, we covered the asynchronous javascript concepts including common patterns used for the purpose. The knowledge of JavaScript fundamentals, we acquired from the tutorial should help us immensely when we take our plunge in automation testing, both frontend and backend.

As always, please do share your thoughts and feedback on this tutorial. I will surely make amends where ever necessary and it will help me improve.