DEV Community

loading...

Crash course in Asynchronous JavaScript (Part 2)

chinmaymhatre profile image Chinmay Mhatre Updated on ・10 min read

Introduction

Welcome back!

Here is what we'll cover in part 2

  • Promises
  • Async and Await
  • Making Http Request
  • API Project

Let us continue from where we left off. We'll start by escaping the callback hell.

Promises

What is a promise?

promise

Promise in JavaScript is like the word promise in the real world . When we promise something there are two things that can happen either the promise is kept(resolved) or the promise is broken(rejected).

Similarly, The Promise object in JavaScript has 3 states :

pending : initial state, neither resolved nor rejected.

resolved : meaning that the operation was completed successfully.

rejected : meaning that the operation failed.

image

Definition : Promise in JavaScript is defined as an object that represents the eventual completion (or failure) of an asynchronous operation .

Woah! So lets break that down.

  • Promise is an object .
  • It is often used during Async operations.
  • Promise objects are returned by asynchronous functions which may not have a value initially but will have the value at some point in the future.

Let us understand this with a fake function.

const fakeRequest = (url) => {
    return new Promise((resolve, reject) => {
        const rand = Math.random();
        setTimeout(() => {
            if (rand < 0.7) {
                resolve('YOUR FAKE DATA HERE');
            }
            reject('Request Error!');
        }, 1000)
    })
}
Enter fullscreen mode Exit fullscreen mode

This function is basically trying to simulate a response from a remote server. When we request data from a server, it might take few seconds before it completes or rejects our request. In a sense, the server is promising us to respond with the data .

When the function is called, a Promise is made by the function. It can either keep(resolve) the promise returning the data or break(reject) the promise and throw an error.

This function is generating a random number between 0 and 1. If the number is less than 0.7, it resolves this promise by using the resolve function with the data as parameter, else the promise is rejected with an error in the reject function.

Now that we have created the Promise , How do we consume it?

  • We use .then() , .catch() on the function calls to consume the promise .

.then

  • We pass a callback function to .then which is executed when the promise is resolved.
fakeRequest("fakeurl").then((data)=>{
 console.log(data)
})
Enter fullscreen mode Exit fullscreen mode
  • The 'data' argument will contain 'YOUR FAKE DATA HERE' if the promise is resolved.

.catch

  • If the promise is rejected, we'll have to catch the error .

catch

  • We attach .catch() after .then() to catch any rejected promises.
fakeRequest("fakeurl").then((data)=>{
 console.log(data)
}).catch((error){
 console.log(error)
})
Enter fullscreen mode Exit fullscreen mode
  • If any error is thrown, 'error' argument will have the value 'Request Error!' .

  • To put is simply .then is for resolve , .catch is for reject.

The main difference between callback functions and promises is that, in promises you attach the callbacks(.then and .catch) to the returned promise object and in callback functions you pass callbacks to the function as arguments(success and failure) .

Do you remember the rainbow function we made using callbacks?
Let's recreate it using promises.

const delayedColorChange = (color, delay) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            document.body.style.backgroundColor = color;
            resolve();
        }, delay)
    })
}
Enter fullscreen mode Exit fullscreen mode

Now, if we want the colors to appear one after the other, we just chain the .then in order. Since, the code within each .then only runs when the previous promise is resolved, it allows us to perform multiple asynchronous tasks (changing background color) synchronously(one after the other).

Something like this :

delayedColorChange('red', 1000)
    .then(() => delayedColorChange('orange', 1000))
    .then(() => delayedColorChange('yellow', 1000))
    .then(() => delayedColorChange('green', 1000))
    .then(() => delayedColorChange('blue', 1000))
    .then(() => delayedColorChange('indigo', 1000))
    .then(() => delayedColorChange('violet', 1000))
Enter fullscreen mode Exit fullscreen mode
  • First delayedColorChange will be called for color red.
  • When it finishes and resolves the promise ".then" delayedColorChange will be called for color orange and so on.

Let's see a real world situation.

  • Sometimes you may need to make multiple API calls or calls to your database for data one after other.
  • Lets assume you have to make calls to two APIs 'A' and 'B', but to call 'B' you'll need to pass data that you get from 'A' .
  • So 'A' needs to be resolved first, right? .then "B" can be called.

  • We can use our fakeRequest function to make these fake API requestS.

  • Here is how we do it :

fakeRequest('urlA')
    .then((dataa) => {
        console.log("(urlA) worked")
        return fakeRequestPromise('UrlB')
    })
    .then((datab) => {
        console.log("(urlB) worked")
    })
    .catch((err) => {
        console.log("OH NO, A REQUEST FAILED!!!")
        console.log(err)
    })

Enter fullscreen mode Exit fullscreen mode
  • This works because .then only runs after promise is resolved . Then we call the urlB API .
  • Now, the next .then is attached for handling urlB
  • If any one of the requests fail (promises are rejected), they just fall through to the .catch.
  • If we want to have more API calls after urlB we just keep on returning the functions and chaining the .then.
fakeRequest('urlA')
    .then((dataa) => {
        console.log("(urlA) worked !!!")
        return fakeRequestPromise('urlB')
    })
    .then((datab) => {
        console.log("(urlB) worked !!!")
        return fakeRequestPromise('urlC')
    })
    .then((datac) => {
        console.log("(urlC) worked !!!")
    })
    .catch((err) => {
        console.log("OH NO, A REQUEST FAILED!!!")
        console.log(err)
    })
Enter fullscreen mode Exit fullscreen mode

The reason for doing this is to simplify the code . We see that both the rainbow function and the fake request function look a lot simpler when we use promises compared to the functions made using callbacks.

Async/Await

  • Async and Await are what programmers call syntactical sugar.
  • It is basically an easier way of using promises.

## Async
The async keyword can be placed before a function.

const greeting = async ()=>{
   return "Nice to meet you!!"
}
Enter fullscreen mode Exit fullscreen mode
  • Writing the async keyword means that now, the function always returns a promise.
  • No matter what kind of values are returned by the function, it is always wrapped in a promise.
  • So, now that the function returns a promise, we can call it using .then .
greeting().then((data)=>{
 console.log(data)
})
Enter fullscreen mode Exit fullscreen mode
  • This will print out "Nice to meet you!!".
  • Thus we do not need to explicitly resolve the promise .

## Await

  • Await keyword pauses the code within the async function till the promise is resolved.
  • Await is only used within an async function .
  • If we use the Await keyword outside the async function it will be a syntax error.
 let result = await promise;
Enter fullscreen mode Exit fullscreen mode
  • Let us call our rainbow function delayedColorChange()but now using async and await .

  • First, we create an async function that will call the functions for the different colors.

const rainbow = async ()=>{

}
Enter fullscreen mode Exit fullscreen mode
  • Let's call delayedColorChange for the red color .
  • After the red color, the function should return with "Printed"
const printRainbow = async ()=>{
  delayedColorChange('red', 1000)
  return "Printed"
}
Enter fullscreen mode Exit fullscreen mode
  • However, if we execute it like this, the function won't wait for the red color to be displayed . It will instantaneously print "Printed".
  • Thus we must ask the printRainbow function to wait till delayedColorChange('red', 1000) resolves it's promise.
    waiting

  • To do this, we put the await keyword in front of the delayedColorChange.

  • This will ask the printRainbow to wait till the promise is resolved.

const printRainbow = async ()=>{
  await delayedColorChange('red', 1000)
  return "Printed"
}
Enter fullscreen mode Exit fullscreen mode
  • Let us put the rest of the colors in .
const printRainbow = async ()=>{
  await delayedColorChange('red', 1000)
  await delayedColorChange('orange', 1000)
  await delayedColorChange('yellow', 1000)
  await delayedColorChange('green', 1000)
  await delayedColorChange('blue', 1000)
  await delayedColorChange('indigo', 1000)
  await delayedColorChange('violet', 1000)
  return "Printed"
}

printRainbow().then((data)=>(
  console.log(data)
))
Enter fullscreen mode Exit fullscreen mode
  • After all the colours are shown the console prints "Printed".

To handle the errors we can use try catch blocks

const printRainbow = async ()=>{
   try{
        await delayedColorChange('red', 1000)
        return "Printed"
   } 
    catch{
        console.log("error")
    }
}
Enter fullscreen mode Exit fullscreen mode

We can compare it with the callback version of the function .

//Promises (Async and Await)
const printRainbow = async ()=>{
  await delayedColorChange('red', 1000)
  await delayedColorChange('orange', 1000)
  await delayedColorChange('yellow', 1000)
  await delayedColorChange('green', 1000)
  await delayedColorChange('blue', 1000)
  await delayedColorChange('indigo', 1000)
  await delayedColorChange('violet', 1000)
  return "Printed"
}

//Callbacks
delayedColorChange('red', 1000, () => {
    delayedColorChange('orange', 1000, () => {
        delayedColorChange('yellow', 1000, () => {
            delayedColorChange('green', 1000, () => {
                delayedColorChange('blue', 1000, () => {
                    delayedColorChange('indigo', 1000, () => {
                        delayedColorChange('violet', 1000, () => {
                             //This function will be empty since 
                             //we want to end the 
                            //color change
                        })
                    })
                })
            })
        })
    })
});

Enter fullscreen mode Exit fullscreen mode

As we can see it is much cleaner and compact.

Making HTTP request

  • Let us now understand the final piece in the puzzle of async JavaScript.
  • We know how to handle the responses coming from APIs using promises and callbacks.
  • Now, it is time for us to make an actual http request rather than faking it using fakerequest() function.

Request header

HTTP headers are basically ways to pass additional information between the client and server.
headerreq
We can see additional information about the request that is passed through headers are in the form of key and values.

Types of http requests

  • GET : Used to "Get" the data from a resource.
  • POST : Used to send data to a particular destination.
  • PUT : Used to update the existing data.
  • DELETE : Used to delete data.

Mostly we will be using GET request, since we want to get the JSON data from the API.

In the Web API section, the request we made through the browser to the Pokémon API was a GET request, since we 'get' the image data from the API server.

However, to use the data from the web API in our own website we have to make requests through JavaScript.

There are different ways to make request to these web APIs Asynchronously through JavaScript.

  • XMLHttpRequest
  • Fetch API
  • Axios

We'll be using Axios for our webpage. However, let's have an overview of XHR and fetch API as well.

XMLHttpRequest

  • XHR is the original way of making requests using JavaScript.
  • It is a browser API.
  • It is not a preferred method since it uses callbacks and does not support promises.

The basic syntax is as follows:

var xhttp = new XMLHttpRequest();//1
xhttp.onload = function() {//4
  const data = JSON.parse(this.responseText);
};

xhttp.onerror = function() { // only triggers if the request couldn't be made at all
  alert(` Error`);
};
xhttp.open("GET", "filename", true);//2
xhttp.send();//3
Enter fullscreen mode Exit fullscreen mode
  1. We create a xhttp object
  2. xhttp.open("GET", "url", true); opens the request by specifying : type of request, url that is to be requested, if we want the request to be async or not.
  3. We use xhttp.send() to send the request.
  4. we setup a method onload that returns responseText with the data from the request.

Fetch API

Fetch makes it easier to make web requests and handle responses compared to the older XMLHttpRequest.

It is a simpler API which uses promises, avoiding callback hell and having to remember the complex API of XMLHttpRequest.

Syntax

fetch('http://example.com/movies.json')
  .then(response => response.json())
  .then(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode

We pass the URL to the fetch function which returns a promise with the response. However, this is just a HTTP response and not the actual JSON data. To get the JSON content we use .json method on the response. Finally we print the data in the console.


Axios

  • Axios is a promise based HTTP client for the browser and node.js.
  • It is an improvement on the fetch api.

Installing

Axios can be added using a CDN as well as through node.js :

Using CDN

We can attach the below script tag to our html document above our own js file to use Axios.

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
Enter fullscreen mode Exit fullscreen mode

Using npm

$ npm install axios
Enter fullscreen mode Exit fullscreen mode

Usage

Get Request

Making a GET request is as simple as writing axios.get(url) . To handle the responses we use .then() and .catch() since axios uses promises.
we do not need to chain another .then like the fetch API.

axios.get('API URL')
  .then(function (response) {
    // handle success
    console.log(response);
  })
  .catch(function (error) {
    // handle error
    console.log(error);
  })
Enter fullscreen mode Exit fullscreen mode

We may also use async await

const main = async ()=>{
    try{
        let result = await axios.get("url")
        return result
    }catch{
        console.log("request failed");
    }
}
Enter fullscreen mode Exit fullscreen mode

Let us make request to a GitHub API using Axios :

axios.get('https://api.github.com/users/mapbox')
  .then((response) => {
    console.log(response.data);
    console.log(response.status);
    console.log(response.statusText);
    console.log(response.headers);
  });

// logs:
// => {login: "mapbox", id: 600935, node_id: "MDEyOk9yZ2FuaXphdGlvbjYwMDkzNQ==", avatar_url: "https://avatars1.githubusercontent.com/u/600935?v=4", gravatar_id: "", …}

// => 200

// => OK

// => {x-ratelimit-limit: "60", x-github-media-type: "github.v3", x-ratelimit-remaining: "60", last-modified: "Wed, 01 Aug 2018 02:50:03 GMT", etag: "W/"3062389570cc468e0b474db27046e8c9"", …}

Enter fullscreen mode Exit fullscreen mode

We can also make other requests using Axios .

  • axios.request(config)
  • axios.get(url[, config])
  • axios.delete(url[, config])
  • axios.head(url[, config])
  • axios.options(url[, config])
  • axios.post(url[, data[, config]])
  • axios.put(url[, data[, config]])
  • axios.patch(url[, data[, config]])

The some parameters are put into square brackets because they are optional.

API Project

Now, it's time to put together everything we have learnt so far .

Here is what we will be building :
image

What is the project about

This project is a part of Colt Steele's web developer bootcamp course.
It is a website that display tvshows according to the user's input using the tvmaze api.

Code

Here is a link to the completed project : TvShowApi

index.html

  • We create a form and give it a class of search-form to be used later in JavaScript.
  • Add a text input to get the data.
  • A submit button to submit the form.
    <form class="search-form" >
    <input type="text" class="form-control" id="search" placeholder="Search Tvshows">
    <input type="submit" class="form-control mt-2" id="sub" value="Submit">
    </form>
Enter fullscreen mode Exit fullscreen mode

The images that we'll get from the API will be displayed within this div.

<div class="shows">   
</div>
Enter fullscreen mode Exit fullscreen mode

app.js

Select all the required elements using querySelector.

let input = document.querySelector("#search");
let showdiv = document.querySelector('.shows')
let searchForm = document.querySelector('.search-form')
Enter fullscreen mode Exit fullscreen mode

  • We create an event listener which makes the axios request asynchronously each time the form is submitted.
  • After getting the data from axios we pass it to a createImages function which is used to display the images of the different shows we get back from the API.
searchForm.addEventListener("submit",async (e)=>{ //e is a event object 
    e.preventDefault(); //Prevents form from refreshing the page.
    if(input.value != ""){ // Checking if the input is empty.
        try {
            let result = await axios.get(`http://api.tvmaze.com/search/shows?q=${input.value}`)
            createImages(result.data)//result.data is an array
            console.log(result); // You can look at the result from the api in the console
        }
        catch (error) {
            console.log(error);
        }
    }
})
Enter fullscreen mode Exit fullscreen mode

Here is how the response from the API looks like:
image


The createImages function below is used to create the images from the API data.

const createImages = (shows)=>{//shows is an array
    for(show of shows){ 
        if (show.show.image) { // checking if there is an image for the current show
            let image = document.createElement('img')
            image.src = show.show.image.medium // show.show.image.medium contains the url of the image
            showdiv.append(image) //we attach the images to an empty div that we created in html
        }
    };
}
Enter fullscreen mode Exit fullscreen mode

Below image shows the path which we used to get the image url in the createImages function.
image

Conclusion

I hope after reading this, you'll have a better understanding for the different puzzle pieces in the jigsaw puzzle of Async JavaScript.

Now, the next step from here on out would be to try the different concepts in your own code. You can also try out the different code snippets provided in the article and observe the result you get.

Let me know in the comments if you found this helpful .

See you in the next one :)

Discussion (5)

Collapse
tqbit profile image
tq-bit

Great article. Making server requests puzzled the hell out of me when I started learning Javascript.

Perhaps in your next post, you can give async / await a bit more attention and how they go together with promises.

Also, if you're looking for some deeper topics on promises, perhaps you can include Promise.all() and Promise.race(), I'd love to read an article with some use cases about these two guys.

Collapse
chinmaymhatre profile image
Chinmay Mhatre Author

Thank you !! I'll keep that in mind

Collapse
akanbiabubakar profile image
akanbi abubakar

Amazing lecturer!

Collapse
chinmaymhatre profile image
Collapse
retumishra22 profile image
Retumishra22

Simple and Effective!
This is such a well written article, Congratulations !!
Keep posting more advanced articles.
Thank you!

Forem Open with the Forem app