Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 22

Asynchronous Data Fetching with Fetch and Async/Await

Recommended resources

Hello! Let's dive into our next lesson on asynchronous JavaScript.

Introduction

In our last lesson, you mastered how to use try...catch to handle errors within async/await functions. This gives you a robust pattern for writing resilient code that can deal with unexpected issues.

Today, we'll put that pattern to practical use. The learning outcome is to use the Fetch API with async/await to make GET and POST requests to a server. The Fetch API is the modern, built-in tool in browsers for communicating with servers. This skill is the cornerstone of building any web application that interacts with a backend, which is a key part of your goal to connect a React front-end to your Laravel applications.

We'll cover how to retrieve data from an API (a GET request) and how to send data to an API (a POST request), all using the clean async/await syntax you're now familiar with.


The Anatomy of a fetch Call

The fetch() function starts the process of fetching a resource from the network, and it returns a Promise. This is why it works so well with async/await.

A common point of confusion is that a typical fetch call involves two await keywords. Let's look at why.

JavaScript Fetch with Async/Await
This code shows a `fetch` request using `async/await`. The question "// Why waiting twice?" highlights the two-step nature of the process.

The reason we "wait twice" is because fetch is a two-step process:

  1. await fetch(url): The first await pauses your function until the browser receives the initial response from the server. This response includes the HTTP headers and status code, but not the actual data (the response body). The promise resolves with a Response object. This object has useful properties like response.status (e.g., 200, 404) and response.ok (a boolean that is true for statuses in the 200-299 range).

  2. await response.json(): The response body arrives as a "stream" of data. To read this stream to completion and parse it as a JSON object, we use the .json() method on the Response object. Since reading the stream can take time, this method also returns a Promise. The second await pauses your function until the entire body has been downloaded and parsed.


Making GET Requests with async/await

A GET request is used to retrieve data from a server. It's the default method used by fetch.

Let's watch a video that refactors a fetch call from the older .then() syntax to the modern async/await syntax we're focusing on. This will clearly illustrate the benefits.

Fetch API with Async/Await (GET, POST, PUT, DELETE)

The following clip from ByteGrad demonstrates how to convert a standard GET request using fetch to the much cleaner async/await style. Pay attention to how the sequence of operations becomes more linear and easier to read.

Watch the segment from 06:14 to 08:45. Notice how await fetch(...) replaces the first .then() and await response.json() replaces the second one.

As the video shows, the code becomes much more intuitive. Now, let's incorporate the error handling you learned in the last lesson.

async function getUsers() {
  const url = 'https://reqres.in/api/users';

  try {
    // Step 1: Start the request and wait for the response headers
    const response = await fetch(url);

    // Check if the request was successful (status 200-299)
    // This is crucial for handling server errors like 404 (Not Found)
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    // Step 2: Read the response body and parse it as JSON
    const data = await response.json();

    // Now you can work with the data
    console.log('Users:', data.data);
    return data.data;

  } catch (error) {
    // This will catch network errors (e.g., no internet connection)
    // and the error we manually threw above.
    console.error('Could not fetch users:', error);
  }
}

getUsers();

This structure is the complete, modern pattern for making a GET request. All the asynchronous logic and error handling is contained neatly within one async function.


Making POST Requests: Sending Data to a Server

While GET requests are for retrieving data, POST requests are for sending data to a server, like submitting a form or creating a new resource.

To make a POST request with fetch, you need to provide a second argument: an options object that configures the request. The key properties for a POST request are:

  • method: The HTTP method, e.g., 'POST'.
  • headers: An object specifying the request headers. To send JSON, you must include 'Content-Type': 'application/json'.
  • body: The data you want to send. It must be converted to a JSON string using JSON.stringify().

Since you have experience with Laravel, this should feel familiar. You're essentially constructing the client-side version of the HTTP requests your Laravel routes consume.

This next video segment builds on the previous example to show how to configure a POST request.

Fetch API with Async/Await (GET, POST, PUT, DELETE)

Now, let's see how to send data. This clip from the same ByteGrad video explains how to configure the fetch options for a POST request.

Watch the segment from 11:01 to 13:20. Focus on the three key parts added to the fetch call: method, headers, and body.

Here is a complete example of a function that creates a new user by sending data via a POST request.

async function createUser(userData) {
  const url = 'https://reqres.in/api/users';

  try {
    const response = await fetch(url, {
      // 1. Specify the method
      method: 'POST', 
      
      // 2. Set the headers to indicate you're sending JSON
      headers: {
        'Content-Type': 'application/json'
      },

      // 3. Provide the data, stringified into JSON format
      body: JSON.stringify(userData)
    });

    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    // The server often responds with the created object, including its new ID
    const result = await response.json();

    console.log('User created:', result);
    return result;

  } catch (error) {
    console.error('Could not create user:', error);
  }
}

// Example usage:
const newUser = {
  name: 'Alex',
  job: 'Laravel Developer'
};

createUser(newUser);

For a detailed textual reference on the different options you can use with fetch, the following article is an excellent resource.

Fetch - The Modern JavaScript Tutorial

This article from The Modern JavaScript Tutorial provides a comprehensive overview of fetch. I recommend reading the section on POST requests to solidify your understanding of the configuration options.

Read the section titled POST requests. It provides another clear example and reinforces the concepts of method, headers, and body.


Conclusion

You have now learned how to use fetch with async/await to perform the two most common types of API interactions: GET and POST. This is a fundamental skill for building the full-stack applications you're aiming for.

Key Takeaways:

  • fetch() is a modern, Promise-based API for making network requests.
  • Making a request and reading the body are two separate asynchronous steps, which is why we use two await keywords.
  • For GET requests, you just need the URL. For POST requests, you must provide an options object specifying the method, headers (Content-Type), and the body (as a JSON string).
  • Always check the response.ok property to handle server-side errors (like 404 or 500) gracefully inside your try block.
  • The try...catch block is the perfect way to handle both network errors and server errors in a single, clean structure.

Preview of Next Lesson:
In our current examples, we've been waiting for one API call to finish before starting the next. But what if you need to fetch multiple pieces of data at the same time? In the next lesson, we will explore how to use Promise.all() to handle multiple concurrent asynchronous operations efficiently, making your applications faster and more responsive.