DEV Community

Abhinav
Abhinav

Posted on

Understanding Why Fetch Requires await Twice✨

JavaScript's fetch API is widely used for making HTTP requests, but it can be a bit tricky to understand why it sometimes requires two await statements. If you've worked with fetch before, you might have encountered code like this:

const response = await fetch('https://api.example.com/data');
const data = await response.json();
Enter fullscreen mode Exit fullscreen mode

Let’s break this down and understand why this pattern is necessary. 🧐

The Two-Step Process of Fetch 🛠️

The fetch API is designed to handle network requests asynchronously, but its behavior is split into two main stages:

  1. Fetching the Response 🌐

    • When you call fetch, it returns a Promise that resolves to the Response object once the network request is completed.
    • This step doesn’t process the body of the response; it only ensures that the request was successful and headers are available.
  2. Reading the Response Body 📄

    • The Response object has methods like .json(), .text(), and .blob() to read the actual content.
    • These methods also return Promises because reading the body is asynchronous. This is necessary to handle large payloads efficiently without blocking the main thread.

What Happens During the First await? ⏳

When you write const response = await fetch(url);, here’s what happens:

  1. Network Request Sent: 🚀

    • The browser initiates an HTTP request to the specified URL.
    • This involves resolving the domain name, establishing a TCP connection, and sending the HTTP headers and body (for POST requests).
  2. Response Metadata Received: 📬

    • The fetch call resolves once the server responds with the status line (e.g., HTTP/1.1 200 OK) and headers. At this point:
      • The status (e.g., 200, 404, or 500) and statusText (e.g., "OK" or "Not Found") are available.
      • Response headers like Content-Type, Content-Length, and any custom headers sent by the server are accessible.
  3. Response Object Created: 🛠️

    • The browser constructs a Response object that contains metadata about the response. This includes:
      • Headers: Accessible via response.headers, which allows you to inspect specific headers like Content-Type or Authorization.
      • Body: At this point, the body has not been fully read or parsed—it remains as a readable stream.

For example, if the server returns:

   HTTP/1.1 200 OK
   Content-Type: application/json
   Content-Length: 123

   {"message": "Hello, world!"}
Enter fullscreen mode Exit fullscreen mode

The Response object will contain:

  • status: 200 ✅
  • statusText: "OK" ✅
  • headers: An iterable collection of the response headers (e.g., Content-Type: application/json).
  • body: A readable stream that hasn’t been parsed yet.

What Happens During the Second await? 🔄

When you write const data = await response.json();, the following steps occur:

  1. Body Stream Read: 📥

    • The body of the response (still in raw form) is read as a stream. Depending on the method you use, the raw data is processed accordingly:
      • .json(): Parses the stream as JSON and returns a JavaScript object.
      • .text(): Reads the stream as a plain text string.
      • .blob(): Reads the stream as a binary large object.
  2. Parsing and Resolving: 🧩

    • The json() method parses the raw data (e.g., {"message": "Hello, world!"}) into a usable JavaScript object (e.g., { message: "Hello, world!" }).
    • This parsing process is asynchronous because it involves processing potentially large data.
  3. Promise Resolution:

    • The Promise returned by response.json() resolves to the parsed data, which can then be used in your application.

Why Two await Statements Are Needed 🤔

Here’s the reason you need await twice:

  1. First await (Waiting for the Response):

    • The fetch call doesn’t immediately provide the response data; it gives you a Promise. You need to await it to get the Response object.
  2. Second await (Parsing the Body):

    • The .json() method (or other body-reading methods) returns another Promise. You need to await this to extract the parsed content.

If you skip either await, you’ll likely end up with unexpected behavior:

  • Skipping the first await: You’ll be working with the unresolved Promise instead of the actual Response object.
  • Skipping the second await: You’ll get a Promise instead of the parsed data.

Example with Error Handling 🛡️

Here’s how you might handle errors properly while working with fetch:

try {
  const response = await fetch('https://api.example.com/data');
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error('Error fetching data:', error);
}
Enter fullscreen mode Exit fullscreen mode

Common Pitfalls ⚠️

  1. Not Handling Errors:

    • fetch doesn’t throw an error for HTTP errors like 404 or 500. You must check response.ok or response.status manually.
  2. Skipping the Second await:

    • Forgetting to await .json() can lead to bugs where you’re working with a Promise instead of actual data.
  3. Confusion Between fetch and Older APIs:

    • Developers transitioning from older APIs like XMLHttpRequest might expect synchronous behavior, but fetch is entirely Promise-based.

Conclusion 🎯

Using two await statements with fetch might seem redundant at first, but it’s a logical outcome of its asynchronous design. The first await ensures the response has been received, including headers and metadata, while the second await processes the response body. Understanding this flow helps you write more reliable and maintainable asynchronous code. 🚀

Top comments (2)

Collapse
 
juniourrau profile image
Ravin Rau

Interesting article, it is a question that I have in mind also. The step-by-step explanation of the two stages—fetching the response and reading the body—is super clear and helpful for understanding how the API works under the hood.

With fetch being so widely used, do you think there’s room for improvement in simplifying this two-step process, or does the current design strike the right balance between flexibility and simplicity?

Collapse
 
jeffreymk0213 profile image
Jeffrey

I learned a kind of basic knowledge before Chrismas. Thanks.