SKU // 132

JS Fetch API

You have already used fetch to load JSON. This lesson covers the rest of the tool: the options that turn a plain request into a POST with a body and headers, the pieces of a Request and a Response, reading a body, and cancelling a request that is taking too long. Several examples build Request and Response objects directly, so you can study them without needing a server.

// The Options Object

fetch(url) makes a plain GET request. The second argument is an options object that changes that:

Option Controls
method"GET" (default), "POST", "PUT", "PATCH", "DELETE"
headersextra information about the request, like "Content-Type"
bodythe data to send (a string for JSON, or a FormData)
signallets you cancel the request (see below)
credentialswhether to send cookies: "same-origin" (default), "include", "omit"
async function postScore() {
  const response = await fetch("/api/scores", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name: "Ava", score: 95 })
  });
  return response.json();
}

There is no Try It button for this one because it needs a server that accepts scores. The next examples let you see exactly what such a request holds, with no server at all.

// Looking Inside a Request

new Request(url, options) builds the same request object fetch makes for you, without sending it. You can read what you set, and read the body back:

const request = new Request("https://example.com/api/scores", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Ava", score: 95 })
});

console.log(request.method);
console.log(request.url);
console.log(request.headers.get("content-type"));

request.json().then(data => console.log(data.score));
POST https://example.com/api/scores application/json 95

// Looking Inside a Response

A Response is what comes back. You can build one yourself to practice with its parts: the status, the headers, and the body readers:

const response = new Response(JSON.stringify({ name: "Ava", score: 95 }), {
  status: 201,
  statusText: "Created",
  headers: { "Content-Type": "application/json" }
});

console.log(response.status, response.statusText, response.ok);
console.log(response.headers.get("content-type"));

response.json().then(data => console.log(data.name));
201 Created true application/json Ava
Property Holds
response.statusthe number: 200, 404...
response.statusTextthe short text that goes with it
response.oktrue for 200 to 299
response.headersa Headers object (names are case-insensitive)
response.urlthe final address, after any redirects
response.redirectedtrue if a redirect happened

// Reading the Body: Once Only

The body can be read in one form: .json(), .text(), .blob() (for files), or .formData(). It can be read only once. A second read throws an error, because the data has been used up. If you need it twice, make a .clone() first:

async function demo() {
  const response = new Response("hello");
  const copy = response.clone();

  console.log(await response.text());
  console.log(response.bodyUsed);

  try {
    await response.text();
  } catch (error) {
    console.log(error.name);
  }

  console.log(await copy.text());
}

demo();
hello true TypeError hello

// Headers

A Headers object holds the headers. Names are case-insensitive, and you read and change them with methods:

const headers = new Headers({ "Content-Type": "application/json" });
headers.set("X-Student", "Ava");

console.log(headers.get("content-type"));
console.log(headers.has("x-student"), headers.has("x-other"));

for (const [name, value] of headers) {
  console.log(name + ": " + value);
}
application/json true false content-type: application/json x-student: Ava

// Cancelling a Request

A request that takes too long can be cancelled with an AbortController. Give its signal to fetch, then call abort(). The pending fetch rejects with an error named AbortError:

async function demo() {
  const controller = new AbortController();
  controller.abort();

  try {
    await fetch("students.json", { signal: controller.signal });
  } catch (error) {
    console.log(error.name);
  }
}

demo();
AbortError

A common use is a timeout, or cancelling the old request when a user types a new search. AbortSignal.timeout(5000) makes a signal that aborts by itself after five seconds:

fetch(url, { signal: AbortSignal.timeout(5000) })
  .catch(error => {
    if (error.name === "TimeoutError") console.log("Took too long");
  });

// A Reusable Helper

Here are the lessons from this section and the JSON one in a single function: check ok, throw a clear error, parse the JSON, and accept options:

async function getJSON(url, options) {
  const response = await fetch(url, options);
  if (!response.ok) {
    throw new Error(url + " answered " + response.status);
  }
  return response.json();
}

async function main() {
  const students = await getJSON("students.json");
  console.log(students.length, "students");
  try {
    await getJSON("missing.json");
  } catch (error) {
    console.log(error.message);
  }
}

main();
4 students missing.json answered 404

// Common Mistakes

// Quick Check

How do you send a JavaScript object as JSON in a POST request?

  1. body: object
  2. body: JSON.stringify(object) and a JSON Content-Type header
  3. body: JSON.parse(object)
  4. Put the object in the URL
Show answer
B. The body must be text. Stringify the object and tell the server it is JSON with the Content-Type header.

What happens if you call response.json() twice on the same response, and how do you avoid it?

Show answer
The second call throws a TypeError, because the body can be read only once. Call response.clone() first if you need to read it twice.

What does this log?

const c = new AbortController();
c.abort();
fetch("students.json", { signal: c.signal })
  .catch(error => console.log(error.name));
Show answer
AbortError. The signal was already aborted, so the request is cancelled and the promise rejects with that error name.

// Try It

  1. Click Open Playground and run it. Which line proves that a Response body is used up after one read?
  2. Build a Request with the PUT method and a body, then log its method and body
  3. Build a Response with status 404 and check what ok says
  4. Use getJSON on products.json and log the store name
// Video Walkthroughs
Last updated: Oct 11, 2026, 8:04 PM ET