JavaScript Fetch API

The Fetch API provides a modern, promise-based way to make HTTP requests in JavaScript. It replaces the older XMLHttpRequest and provides a cleaner, more powerful interface for fetching resources across the network.

fetch() Basics

The fetch() function takes a URL as its first argument and returns a Promise that resolves to a Response object. The simplest use is a GET request to a URL.

Basic fetch() Call
// fetch() returns a Promise
// Simulating a fetch response
const mockResponse = {
  ok: true,
  status: 200,
  json: () => Promise.resolve({ userId: 1, title: 'Hello' })
};

// In a real app:
// fetch('https://api.example.com/data')
//   .then(response => response.json())
//   .then(data => console.log(data));

// Simulated version:
Promise.resolve(mockResponse)
  .then(function(response) {
    console.log('Status: ' + response.status);
    console.log('OK: ' + response.ok);
    return response.json();
  })
  .then(function(data) {
    console.log('Data: ' + JSON.stringify(data));
  });

GET and POST Requests

By default, fetch() makes a GET request. To make a POST request, pass an options object as the second argument with the method, headers, and body.

GET vs POST Configuration
// GET request (default)
const getOptions = {
  method: 'GET',
  headers: {
    'Accept': 'application/json'
  }
};
console.log('GET options: ' + JSON.stringify(getOptions));

// POST request
const postData = { name: 'Alice', email: 'alice@example.com' };
const postOptions = {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(postData)
};
console.log('POST options: ' + JSON.stringify(postOptions));
console.log('POST body: ' + postOptions.body);

Response Methods

The Response object has several methods to extract the body content: response.json() for JSON, response.text() for plain text, response.blob() for binary data, and response.formData() for form data.

MethodReturnsUse Case
response.json()Promise<Object>Parse JSON response body
response.text()Promise<String>Get plain text response
response.blob()Promise<Blob>Binary data (images, files)
response.formData()Promise<FormData>Form data responses
response.arrayBuffer()Promise<ArrayBuffer>Raw binary buffer

Headers

You can set and read HTTP headers using the Headers object or a plain object. Headers are used for content type negotiation, authentication, and other metadata.

Working with Headers
// Creating headers with an object
const myHeaders = new Headers();
myHeaders.append('Content-Type', 'application/json');
myHeaders.append('Authorization', 'Bearer token123');

console.log('Content-Type: ' + myHeaders.get('Content-Type'));
console.log('Has Auth: ' + myHeaders.has('Authorization'));

// Headers can also be a plain object
const options = {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'myValue'
  }
};
console.log('Method: ' + options.method);
console.log('Custom Header: ' + options.headers['X-Custom-Header']);

Error Handling

Important: fetch() only rejects on network errors, not on HTTP error statuses like 404 or 500. You must check response.ok or response.status to handle HTTP errors.

Proper Error Handling with fetch
async function safeFetch(url) {
  try {
    // Simulating fetch behavior
    const response = { ok: false, status: 404, statusText: 'Not Found' };

    if (!response.ok) {
      throw new Error('HTTP Error: ' + response.status + ' ' + response.statusText);
    }

    return response;
  } catch (error) {
    console.log('Caught error: ' + error.message);
  }
}

safeFetch('https://api.example.com/missing');

// Network error example
async function handleNetworkError() {
  try {
    throw new TypeError('Failed to fetch');
  } catch (error) {
    console.log('Network error: ' + error.message);
  }
}

handleNetworkError();

Async/Await with fetch

The Fetch API works seamlessly with async/await, making the code cleaner and easier to read than chaining .then() calls.

fetch with async/await
async function getUser(id) {
  // Simulating an API call
  const users = {
    1: { id: 1, name: 'Alice', role: 'Admin' },
    2: { id: 2, name: 'Bob', role: 'User' }
  };

  // In a real app: const response = await fetch(url);
  const user = users[id];

  if (!user) {
    throw new Error('User not found');
  }

  return user;
}

async function main() {
  try {
    const user = await getUser(1);
    console.log('Name: ' + user.name);
    console.log('Role: ' + user.role);

    const missing = await getUser(99);
  } catch (error) {
    console.log('Error: ' + error.message);
  }
}

main();
📝 Note: fetch() does NOT reject on HTTP error status codes (404, 500, etc.). It only rejects on network failures. Always check response.ok before processing the response. The AbortController can be used to cancel fetch requests with controller.abort() and passing the signal to the fetch options.
Exercise:
When does fetch() reject its promise?
Try it YourselfCtrl+Enter to run
Click Run to see the output here.