Tutorials Logic, IN info@tutorialslogic.com

Fetch API: Requests, Errors, Cancellation, and Retries

Fetch Request Flow

fetch() starts an HTTP request and resolves to a Response when headers arrive. It rejects for network or cancellation failures, but an HTTP 404 or 500 still resolves, so application code must check response.ok or response.status.

A reliable request also defines cancellation, decoding, credentials, error messages, and whether repeating the operation is safe.

The Fetch API is a Promise-based browser interface built around Request, Response, and Headers objects. A fetch promise resolves when response headers arrive even for HTTP errors, so check `response.ok` or `response.status` before decoding the body.

Basic Fetch GET and POST

Basic Fetch GET and POST
// ---- GET request ----
fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then(response => {
    console.log('Status:', response.status);       // 200
    console.log('OK?', response.ok);               // true
    console.log('Type:', response.type);           // "cors" or "basic"
    return response.json();                        // returns a Promise
  })
  .then(post => console.log(post.title))
  .catch(err => console.error('Fetch failed:', err));

// ---- POST request ----
fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    title: 'Hello World',
    body: 'This is the post body',
    userId: 1
  })
})
  .then(res => res.json())
  .then(data => console.log('Created post with id:', data.id));

Response Methods

The Response object returned by fetch() provides several methods to read the body. Each method returns a Promise and can only be called once per response.

  • response.json() - parses the body as JSON and returns a JavaScript object.
  • response.text() - returns the body as a plain string.
  • response.blob() - returns the body as a Blob (for binary data like images).
  • response.arrayBuffer() - returns the body as an ArrayBuffer.
  • response.formData() - returns the body as a FormData object.
  • response.ok - true if status is 200-299.
  • response.status - the HTTP status code.

Response Methods: json, text, blob

Response Methods: json, text, blob
// response.json()
fetch('/api/users')
  .then(res => res.json())
  .then(users => console.log(users));

// response.text() - useful for HTML fragments or plain text
fetch('/api/message')
  .then(res => res.text())
  .then(text => document.getElementById('msg').textContent = text);

// response.blob() - download and display an image
fetch('/images/avatar.png')
  .then(res => res.blob())
  .then(blob => {
    const url = URL.createObjectURL(blob);
    document.getElementById('avatar').src = url;
  });

// Checking response.ok before processing
fetch('/api/data')
  .then(res => {
    if (!res.ok) {
      throw new Error(`HTTP ${res.status}: ${res.statusText}`);
    }
    return res.json();
  })
  .then(data => console.log(data))
  .catch(err => console.error(err.message));

Headers Object

The Headers class provides a convenient interface for working with HTTP headers in both requests and responses.

Using the Headers Object

Using the Headers Object
// Build headers with the Headers constructor
const myHeaders = new Headers();
myHeaders.append('Content-Type', 'application/json');
myHeaders.append('Authorization', 'Bearer my-token-here');
myHeaders.append('X-Custom-Header', 'my-value');

fetch('/api/secure', {
  method: 'POST',
  headers: myHeaders,
  body: JSON.stringify({ data: 'payload' })
});

// Read response headers
fetch('/api/info').then(res => {
  console.log(res.headers.get('Content-Type'));
  console.log(res.headers.get('X-Rate-Limit'));

  // Iterate all response headers
  res.headers.forEach((value, name) => {
    console.log(`${name}: ${value}`);
  });
});

AbortController - Cancelling a Fetch Request

AbortController - Cancelling a Fetch Request
// AbortController lets you cancel a fetch request
const controller = new AbortController();
const signal = controller.signal;

// Cancel after 5 seconds
const timeoutId = setTimeout(() => controller.abort(), 5000);

fetch('/api/slow-data', { signal })
  .then(res => res.json())
  .then(data => {
    clearTimeout(timeoutId); // clear timeout if request succeeds
    console.log('Data:', data);
  })
  .catch(err => {
    if (err.name === 'AbortError') {
      console.warn('Request was cancelled (timeout or manual abort)');
    } else {
      console.error('Fetch error:', err);
    }
  });

// Cancel manually (e.g., user clicks a cancel button)
document.getElementById('cancel-btn').addEventListener('click', () => {
  controller.abort();
  console.log('Request cancelled by user');
});

Credentials and CORS

For same-origin requests, browser credentials are included by default. A cross-origin request needs credentials: "include" when cookies or HTTP authentication must travel, and the server must return compatible CORS headers. The client cannot fix a missing Access-Control-Allow-Origin response.

A custom header or non-simple content type may trigger a preflight OPTIONS request. Authentication cookies should still use Secure, HttpOnly, and an appropriate SameSite policy, and state-changing requests need CSRF protection.

Timeouts and Retries

Fetch has no universal elapsed-time timeout option; abort it with an AbortSignal. Distinguish user cancellation from a timeout so the interface can respond accurately, and always cancel obsolete work when a view changes.

Retry only transient failures and only when the operation is idempotent or the API supports an idempotency key. Honor Retry-After, use bounded exponential backoff with jitter, and never create an endless retry loop.

Failure Retry? Reason
Network interruption Sometimes A bounded retry may recover.
429 or temporary 5xx Sometimes Follow server guidance and a retry budget.
400 validation error No The request must change.
Unkeyed payment POST No Repeating may duplicate the side effect.

Response Boundaries

  • Read a response body once, or clone the Response before two independent consumers.
  • Check Content-Type before assuming an error body is JSON.
  • Validate decoded fields before updating application state.
  • Revoke object URLs created for downloaded Blob values.
  • Use streams only when incremental processing justifies the additional decoder and cancellation logic.
Before you move on

Fetch Review

5 checks
  • HTTP status, content type, and response shape are checked.
  • The view can cancel work that is no longer useful.
  • Credential and CSRF behavior is explicit.
  • Retries are bounded and safe for the operation.
  • Loading, empty, error, and success states are all represented.

Fetch Failures

  • 404 treated as success

    Check response.ok before decoding the success representation.
  • Every POST is retried

    Retry only an idempotent operation or one protected by an idempotency key.
  • Old request overwrites new state

    Abort obsolete work or track which request owns the result.
  • Raw server error shown

    Map technical details to a safe, actionable message.

Try this next

Build a Reliable Request

0 of 2 completed

  1. Cancel the previous search request whenever the query changes. Treat AbortError as cancellation, not a visible failure.
  2. Retry a read twice for 503 while honoring Retry-After. Stop immediately for a 400 response.
Next Step
Next Practice

Finish the concept here, then reinforce it with hands-on coding, interview prep, or a tool that matches the topic.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.