Skip to content

Latest commit

Β 

History

History
320 lines (255 loc) Β· 12.3 KB

async-await-axios-intro.md

File metadata and controls

320 lines (255 loc) Β· 12.3 KB

Axios, Promises, async await

Learning Objectives

  • Understand the benefits of using Axios for making HTTP requests in Node.js
  • Learn how to use Axios for GET requests and compare it to the built-in Fetch API
  • Gain an understanding of async/await vs. ``.then` for handling asynchronous code
  • Explore how to refactor code to avoid callback hell and improve readability using async/await
  • Complete lab challenges that provide practice in refactoring Axios requests with async/await and Promise.all

Using Axios for GET Requests

axios is a popular library for making HTTP requests in node (and javascript). Since there is no window in Node.js, we don't have access to window.fetch out of the box. Node.js does have a built in requests module and support for XMLHttpRequests, they are quite clunky to use and axios is one of the most popular ways to make HTTP requests in node.

Getting Setup

  • mkdir hello-axios to create a directory for this lesson
  • cd hello-axios to enter the new directory
  • npm init -y to create a package.json
  • npm i axios to install the axios package
  • if you want to make this a repo tracked by git
    • echo node_modules >> .gitignore to not track your installed packages
    • git init to create a new repo

Using axios

Here's a simple example of how to use axios to make a GET request:

// require the package
const axios = require('axios')

// the syntax is axios.httpMethod("url")
axios.get('https://jsonplaceholder.typicode.com/posts')
  .then(response => {
    console.log(response.data);
  })
  .catch(error => {
    console.error(error);
  });

Look, there is only one .then!

catbug happy

One of the benefits of using axios over fetch is that it supports automatic transformation of response data. This is why only one .then is needed. For example, if the response data is JSON, axios will automatically parse it and return a JavaScript object. With fetch, you have to manually call response.json() to parse the response data.

Look in the .then to see another notable difference of using axios over fetch:

  .then(response => {
    console.log(response.data);
  })

We have to dot into the response object to get the api response data. Axios always puts the information sent back to us in a key called data. Try console.loging just the response. What is all that stuff in there?

Here's an example of how to use fetch to make the same GET request, for comparison:

fetch('https://jsonplaceholder.typicode.com/posts')
  .then(response => {
    return response.json();
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error(error);
  });

In this example, we're also making a GET request to the same endpoint, but we have to call response.json() to parse the response data before we can use it.

While fetch is built into modern browsers and has a simple API, axios provides a more comprehensive API with features such as automatic transformation of response data, support for request cancellation, and error handling. Ultimately, the choice between axios and fetch depends on the specific requirements of your project.

async/await vs .then

async/await was added to JavaScript in the ECMAScript 2017 (ES8) specification. It was introduced to make it easier to write asynchronous code using promises, which were introduced in ES6 (along with .then).

Before async/await, developers used promises or callbacks to handle asynchronous operations. Promises are a great improvement over callbacks, but they can still be difficult to use and understand, especially when dealing with complex or nested asynchronous operations.

async/await and .then are both ways to handle asynchronous code in JavaScript. axios supports both methods of handling asynchronous code, since axios requests return javascript promise objects. Here's an example of how to use async/await with axios.get:

async function fetchData() {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
    console.log(response.data);
  } catch (error) {
    console.error(error);
  }
}

fetchData()

In this example, we're using the async keyword to define a function that fetches data from the https://jsonplaceholder.typicode.com/posts endpoint using axios.get. The await keyword can only be used in a function that has been defined as async. async functions must be invoked just like any other function, in order to run the code inside of them.

We're using await to wait for the response before continuing with the rest of the code. If there's an error, we catch it and log it to the console using the try/catch blocks, which act like if/else but only if an error is thrown. In javascript, try/catch can be used with any code, not just async/await.

try {
    // some code that might throw an error
    throw new Error('oops!') // uh-oh, something bad has happened!
} catch (err) {
    // phew! we 'caught' it
    console.log(err)
}

Here's the same example using .then:

axios.get('https://jsonplaceholder.typicode.com/posts')
  .then(response => {
    console.log(response.data);
  })
  .catch(error => {
    // .catch works exclusively with .then, unlike try/catch and async/await
    console.error(error); 
  });

The main difference between async/await and .then is in how they handle asynchronous code. async/await allows you to write asynchronous code that looks more like synchronous code, making it easier to read and understand. Originally .then syntax and promises where introduced to prevent callback hell, however nesting .thens can put you in a similar situation as callback hell, and async/await prevents this issue for the tradeoff of a little bit of boilerplate code.

.then is still useful, and shouldn't just tossed in the garbage bin. It is wise to consider the context of the code when you choose one method of handling asynchronous code over another. For example, It might not be worth it to go through the trouble of a setting up an async function with a try/catch for one simple promise. .then also lets you handle each promise resolution with a .catch instead of having one catch block handle every error case, which can also be useful.

Callback hell

going to hell

Nesting .then can lead to callback hell πŸ˜΅β€πŸ’«

const axios = require('axios');

axios.get('https://swapi.dev/api/people/1/')
    .then(response => {
        const homeworldURL = response.data.homeworld;

        axios.get(homeworldURL)
            .then(response => {
                const population = response.data.population;

                axios.get(`https://swapi.dev/api/planets/?search=${response.data.name}`)
                    .then(response => {
                        const residentsURLs = response.data.results[0].residents;

                        Promise.all(residentsURLs.map(url => axios.get(url)))
                            .then(responses => {
                                const residents = responses.map(res => res.data.name);

                                console.log(`Name: ${response.data.name}`);
                                console.log(`Homeworld Population: ${population}`);
                                console.log(`Residents: ${residents.join(', ')}`);
                            })
                            .catch(error => {
                                console.log('Error fetching residents', error);
                            });
                    })
                    .catch(error => {
                        console.log('Error fetching planet', error);
                    });
            })
            .catch(error => {
                console.log('Error fetching homeworld', error);
            });
    })
    .catch(error => {
        console.log('Error fetching character', error);
    });

async/await to the rescue! πŸ™Œ

const axios = require('axios');

async function fetchCharacterInfo() {
    try {
        const response = await axios.get('https://swapi.dev/api/people/1/');
        const homeworldURL = response.data.homeworld;

        const homeworldResponse = await axios.get(homeworldURL);
        const population = homeworldResponse.data.population;

        const planetResponse = await axios.get(`https://swapi.dev/api/planets/?search=${homeworldResponse.data.name}`);
        const residentsURLs = planetResponse.data.results[0].residents;

        const residentsResponses = await Promise.all(residentsURLs.map(url => axios.get(url)));
        const residents = residentsResponses.map(res => res.data.name);

        console.log(`Name: ${response.data.name}`);
        console.log(`Homeworld Population: ${population}`);
        console.log(`Residents: ${residents.join(', ')}`);
    } catch (error) {
        console.log('Error fetching data', error);
    }
}

fetchCharacterInfo();

Lab: Challenge Problems for Refactoring Axios

For the following challenges, refactor these requests to use async/await syntax instead of .then. You will need to create a new npm project and install the axios package.

Single Requests

axios.get('https://swapi.dev/api/films/1/')
  .then(response => {
    console.log(response.data.title);
    console.log(response.data.director);
    console.log(response.data.release_date);
  })
  .catch(error => {
    console.log(error);
  });
axios.get('https://swapi.dev/api/planets/?page=1')
  .then(response => {
    response.data.results.forEach(planet => {
      console.log(planet.name);
      console.log(planet.climate);
      console.log(planet.terrain);
    });
  })
  .catch(error => {
    console.log(error);
  });

.then Chaining

axios.get('https://swapi.dev/api/films/1/')
  .then(filmResponse => {
    console.log(filmResponse.data.title);
    return axios.get(filmResponse.data.characters[0]);
  })
  .then(characterResponse => {
    console.log(characterResponse.data.name);
    return axios.get(characterResponse.data.homeworld);
  })
  .then(homeworldResponse => {
    console.log(homeworldResponse.data.name);
  })
  .catch(error => {
    console.log(error);
  });

Nested .then

axios.get('https://swapi.dev/api/planets/2/')
  .then(response => {
    console.log(response.data.name);
    axios.get(response.data.residents[0])
      .then(residentResponse => {
        console.log(residentResponse.data.name);
	  })
      .catch(error => {
        console.log(error);
      });
  })
  .catch(error => {
    console.log(error);
  });

Bonus: using Promise.all

Research how Promise.all works before working on these. MDN's article might a be good place to start.

axios.get('https://swapi.dev/api/species/3/')
  .then(speciesResponse => {
    console.log(speciesResponse.data.name);
    return Promise.all(speciesResponse.data.people.map(personUrl => axios.get(personUrl)));
  })
  .then(personResponses => {
    personResponses.forEach(personResponse => {
      console.log(personResponse.data.name);
    });
  })
  .catch(error => {
    console.log(error);
  });
const starshipIds = [2, 9, 10];

const promises = starshipIds.map(id => axios.get(`https://swapi.dev/api/starships/${id}`));

Promise.all(promises)
  .then(responses => {
    responses.forEach(response => {
      console.log(response.data.name);
      Promise.all(response.data.pilots.map(pilotUrl => axios.get(pilotUrl)))
        .then(pilotResponses => {
          pilotResponses.forEach(pilotResponse => {
            console.log(`- ${pilotResponse.data.name}`);
          });
        })
        .catch(error => {
          console.log(error);
        });
    });
  })
  .catch(error => {
    console.log(error);
  });