Using APIs in JavaScript: Fetch, Async/Await

Building on our understanding of asynchronous JavaScript and promises, let's explore how we can use JavaScript APIs to fetch data from APIs.

APIs (Application Programming Interfaces) act like secret doorways, allowing us to retrieve data such as weather information, user profiles, or Pokémon details from web servers.

An API is a set of rules that allows one program to interact with another. These interactions usually happen over the internet using HTTP requests for web APIs. APIs can provide data, such as user information, weather updates, or, in our example, Pokémon details (you'll see how when we fetch Pokémon details shortly.)

Here's a short video where I explain APIs in a little more detail:

JavaScript makes it super easy to work with APIs using the fetch function, and when you pair it with async/await, it becomes even more straightforward. Let’s dive into how this works, especially if it's your first time dealing with APIs and async code.

What is Async/Await?

async/await is a modern syntax introduced to work with promises, making asynchronous code easier to write and understand.

Instead of chaining .then() and .catch() methods, async/await allows you to write async code that looks more like synchronous code.

Let's break down what async and await do and how they compare to traditional promise handling:

Async

  • Async: When you prefix a function with async, it allows the use of await inside the function. This transformation means that the function will always return a promise, even if the function doesn't explicitly return a promise. If the function returns a value, JavaScript automatically wraps it in a resolved promise. If the function throws an error, it returns a rejected promise with the thrown error.
// Without async/await
function getGreeting() {
  return Promise.resolve("Hello, world!");
}

getGreeting().then(message => console.log(message));

// With async/await
async function getGreetingAsync() {
  return "Hello, world!";
}

getGreetingAsync().then(message => console.log(message));

In the example above, getGreeting uses a traditional promise to return a greeting, while getGreetingAsync uses the async keyword, automatically returning a promise.

Await

  • Await: The await keyword can only be used inside an async function. It pauses the execution of the function until the promise is resolved. This means you can write code that reads from top to bottom, just like synchronous code, making it more intuitive. When the promise resolves, await returns the result.
// Using Promises
function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Data fetched!");
    }, 2000);
  });
}

fetchData().then(data => console.log(data));

// Using Async/Await
async function fetchDataAsync() {
  const data = await fetchData();
  console.log(data);
}

fetchDataAsync();

In the example above, fetchData returns a promise that resolves after 2 seconds. When using .then(), you handle the result after the promise resolves. In fetchDataAsync, the await keyword pauses the function's execution until fetchData() resolves, then assigns the result to data and logs it.

This syntax makes the async operation look more like a simple assignment, which can be easier to understand and manage.

Making API Requests with Fetch

The fetch function is like sending a letter asking for information and then waiting for a reply. It returns a promise that resolves with the response.

Here's the Basic Syntax with Async/Await:

async function fetchData(url) {
  let response = await fetch(url);
  let data = await response.json();
  console.log(data);
}

fetchData('https://api.example.com/data');

In this example:

  • fetch(url) sends a request to the URL and waits for a response.
  • await response.json() converts the response into JSON format (a way to handle data that is similar to a JavaScript object).

Let's use the Pokémon API to fetch some data about Pikachu and display it.

async function getPokemonData(pokemon) {
  const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemon}`);
  const data = await response.json();
  console.log(`Name: ${data.name}`);
  console.log('Abilities:');
  data.abilities.forEach(ability => {
    console.log(ability.ability.name);
  });
}

getPokemonData('pikachu');

In this code:

  • We request data for Pikachu from the Pokémon API.
  • await fetch(...) pauses the function until the data is fetched.
  • await response.json() converts the response into a usable JSON object.
  • We then log Pikachu's name and abilities.

Handling Errors with Try/Catch

Now, sometimes things can go wrong. Maybe the API is down, the internet connection drops, or the data we’re looking for doesn’t exist. This is where error handling comes in.

We can handle errors using try...catch blocks in JavaScript.

What is Try/Catch?

  • Try: You put the code that might throw an error inside a try block.
  • Catch: If an error occurs, the control jumps to the catch block, where you can handle it, like logging it or showing a message to the user.

Let's extend our Pokémon example to ensure it doesn't break our app if something goes wrong:

async function getPokemonData(pokemon) {
  try {
    const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemon}`);
    if (!response.ok) {
      throw new Error(`Could not find Pokémon: ${pokemon}`);
    }
    const data = await response.json();
    console.log(`Name: ${data.name}`);
    console.log('Abilities:');
    data.abilities.forEach(ability => {
      console.log(ability.ability.name);
    });
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}

getPokemonData('pikachu');
  • The try block contains the fetch logic. If something goes wrong, such as a network error or the Pokémon not being found, the code inside the catch block runs.
  • The catch block catches the error and logs it to the console so you can see what went wrong.

Jump over to CodePen and try trigger the error by adding a random name to our getPokemonData function to trigger an error.

Handling Multiple Async Operations

Sometimes, you might need to fetch multiple data sets or make several API calls. You can manage this by awaiting multiple results. Here's an example of fetching data for several Pokémon:

const pokemonNames = ["pikachu", "bulbasaur", "charmander"];

async function getPokemonData(pokemon) {
  try {
    let response = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemon}`);
    if (!response.ok) {
      throw new Error(`Could not find Pokémon: ${pokemon}`);
    }
    let data = await response.json();
    console.log(`Name: ${data.name}`);
    console.log('Abilities:');
    data.abilities.forEach(ability => {
      console.log(ability.ability.name);
    });
  } catch (error) {
    console.error("Error fetching data:", error);
  }
}

pokemonNames.forEach(name => getPokemonData(name));

Explanation:

  • We create a function to fetch data for each Pokémon.
  • We then loop over each name in pokemonNames, fetching and displaying each Pokémon’s data.

Full Example: Fetching Pokémon Data

For a more hands-on experience, here's a full example that you can try on CodePen or any other online editor. This example will fetch and display details like names, abilities, and images for multiple Pokémon.

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Pokémon Info</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div id="app">
    <h1>Pokémon Info</h1>
    <div class="pokemon-list" id="pokemon-list"></div>
  </div>
  <script src="script.js"></script>
</body>
</html>

CSS (styles.css)

body {
  font-family: Arial, sans-serif;
  background-color: #f8f8f8;
  color: #333;
  text-align: center;
}

#app {
  margin: 20px auto;
  max-width: 600px;
}

h1 {
  color: #ffcb05;
  text-shadow: 2px 2px #2a75bb;
}

.pokemon-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}

.pokemon-card {
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  margin: 10px;
  padding: 20px;
  width: 200px;
  text-align: center;
}

.pokemon-card img {
  width: 100px;
  height: 100px;
}

.pokemon-card h2 {
  font-size: 1.5em;
  color: #333;
}

.pokemon-card p {
  margin: 5px 0;
}

JavaScript (script.js)

document.addEventListener('DOMContentLoaded', () => {
  const pokemonList = document.getElementById('pokemon-list');
  const pokemonNames = ['pikachu', 'bulbasaur', 'charmander'];

  async function getPokemonData(pokemon) {
    try {
      let response = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemon}`);
      if (!response.ok) {
        throw new Error(`Could not find Pokémon: ${pokemon}`);
      }
      let data = await response.json();
      displayPokemon(data);
    } catch (error) {
      console.error('Error fetching data:', error);
    }
  }

  function displayPokemon(pokemon) {
    const card = document.createElement('div');
    card.className = 'pokemon-card';

    const img = document.createElement('img');
    img.src = pokemon.sprites.front_default;
    img.alt = pokemon.name;

    const name = document.createElement('h2');
    name.textContent = pokemon.name;

    const abilities = document.createElement('p');
    abilities.textContent = 'Abilities: ' + pokemon.abilities.map(a => a.ability.name).join(', ');

    card.appendChild(img);
    card.appendChild(name);
    card.appendChild(abilities);

    pokemonList.appendChild(card);
  }

  pokemonNames.forEach(name => getPokemonData(name));
});

How It Works

  1. HTML Structure: The HTML provides a container (div with id="pokemon-list") where the Pokémon cards will be displayed.
  2. CSS Styling: The CSS file includes styles for the page and Pokémon cards, creating a visually appealing layout.
  3. JavaScript Fetch Calls:
    • An array of Pokémon names

(pokemonNames) defines which Pokémon to fetch.

  • The getPokemonData function fetches data for each Pokémon using the Pokémon API and calls displayPokemon to create and add a card for each Pokémon.
  • The displayPokemon function creates HTML elements for each Pokémon's image, name, and abilities, and appends these elements to the pokemon-list div.

Using async/await with fetch makes working with APIs in JavaScript straightforward and efficient. It allows you to manage data fetching seamlessly, handle errors gracefully, and keep your code clean and understandable.

Mastering these tools will open up a new world of possibilities, whether you're building projects, exploring APIs, or just having fun with code. Remember, handling errors is just as crucial as fetching data, so always include a catch block to manage any potential issues.

Over the next few sections, we will look at a few ways to clean up your code using more tricks that JavaScript offers.

BeginnerJavaScript
Avatar for Niall Maher

Written by Niall Maher

Founder of Codú - The web developer community! I've worked in nearly every corner of technology businesses: Lead Developer, Software Architect, Product Manager, CTO, and now happily a Founder.

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.