JavaScript Fetch API: Requests and Responses

The Fetch API is a modern, promise-based interface built into JavaScript that allows web browsers to make asynchronous HTTP requests to web servers. This article provides a comprehensive breakdown of how the Fetch API handles network communication, including initiating requests, configuring custom headers and methods, parsing incoming response data, and managing errors effectively.

The Basics of Making a Request

The fetch() method is available on the global window object. At its simplest, it takes a single mandatory argument: the URL path to the resource you want to retrieve.

By default, fetch() performs a standard HTTP GET request:

fetch('https://api.example.com/data')
  .then(response => {
    // Process the response here
  })
  .catch(error => {
    // Handle network errors here
  });

Because fetch() is Promise-based, it allows for clean, non-blocking asynchronous execution using either standard Promise chaining (.then()) or modern async/await syntax.

Handling the HTTP Response

When a fetch request completes successfully, the Promise resolves to a Response object. This object does not immediately contain the actual body data in readable format; instead, it represents the entire HTTP response, including metadata.

1. Reading Headers and Status

The Response object provides several properties to inspect the server’s reply: * response.status: The numeric HTTP status code (e.g., 200, 404, 500). * response.ok: A boolean that returns true if the status code is within the 200–299 range. * response.headers: An interface to inspect response headers.

2. Parsing the Response Body

To access the payload, you must call the appropriate body-reading method, which returns another Promise: * response.json(): Parses the response as JSON data. * response.text(): Reads the response as plain text. * response.blob(): Reads the response as raw binary data (ideal for images or files). * response.formData(): Parses multipart form data.

fetch('https://api.example.com/users')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log(data);
  });

Sending Custom HTTP Requests (POST, PUT, DELETE)

To send data or use HTTP methods other than GET, pass an optional configuration object as the second parameter to fetch().

This configuration object accepts several key options: * method: The HTTP verb (e.g., 'POST', 'PUT', 'DELETE'). * headers: An object defining HTTP headers, such as Content-Type or Authorization. * body: The data payload sent to the server (must be a string, FormData, Blob, or BufferSource).

const payload = { username: 'john_doe', role: 'admin' };

fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_TOKEN_HERE'
  },
  body: JSON.stringify(payload)
})
  .then(response => response.json())
  .then(result => console.log('Success:', result))
  .catch(error => console.error('Error:', error));

Handling Errors in Fetch

A crucial distinction in the Fetch API is that a fetch() Promise does not reject on HTTP error statuses such as 404 Not Found or 500 Internal Server Error. Instead, the Promise resolves normally, and the response.ok property is set to false.

The Promise only rejects when a network failure occurs, such as a loss of internet connection, a DNS lookup failure, or a blocked CORS request.

To handle both network failures and server errors, verify the response.ok property explicitly:

async function fetchUserData(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    
    if (!response.ok) {
      throw new Error(`Request failed with status: ${response.status}`);
    }

    const userData = await response.json();
    return userData;
  } catch (error) {
    console.error('Fetch error:', error.message);
  }
}

Canceling Requests with AbortController

The Fetch API supports request cancellation via the AbortController interface. This is useful for preventing stale network requests when user input changes rapidly (such as in autocomplete search bars).

const controller = new AbortController();
const signal = controller.signal;

fetch('https://api.example.com/data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Fetch request was canceled.');
    } else {
      console.error('Other error:', err);
    }
  });

// Cancel the request
controller.abort();