cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

fetch & JSON

Request JSON with fetch, inspect Response status, parse response bodies safely, and tell HTTP errors apart from network failures.

You will learn to
  • 01
    Read an HTTP responseConnect method, URL, headers, body, status, and response.ok.
  • 02
    Parse JSON safelyUse response.json only when there is a JSON body to read.
  • 03
    Handle the right failureSeparate HTTP error statuses from rejected fetch promises.

Requests, replies, and fetch

fetch is the browser’s promise-based way to make network requests. You give it a resource, usually a URL, and optional settings. It gives you a Promise for a Response object once the reply’s headers arrive.

The most common beginner task is: ask for JSON, check whether the server says it worked, then parse the JSON body. That sentence hides three separate waits: the request travels, the response headers arrive, and the body streams in.

Real-life analogyfetch is mailing a letter

Think of HTTP as ordering by mail. The request is your letter: the method says what you want done, the URL is the address, headers are notes on the envelope, and the body is the contents. The response is the reply with a status code stamped on it.

In real life: The letter you send
In JavaScript: The request: method, URL, headers, and body
In real life: The tracking slip
In JavaScript: The Promise returned by fetch
In real life: The reply envelope
In JavaScript: The Response object
In real life: Opening the envelope
In JavaScript: A body method such as response.json()

Where the analogy stops: Mail does not have browser security rules. Real fetch also deals with redirects, credentials, CORS, abort signals, streaming bodies, and caches.

That tracking slip is important. It resolves when the reply envelope arrives, even if the stamp says “404 not found.” It rejects only when the letter never gets there or the browser refuses to hand you the response.

HTTP in a nutshell

MAP

HTTP is the request/response protocol browsers use for web pages, JSON APIs, images, and many other resources. A client sends one request. A server sends one response. fetch is JavaScript’s convenient handle for that exchange.

Request and response anatomy
PieceRequestResponse
Start lineGET /api/users/42 asks for an address with a method.HTTP 200 OK or 404 Not Found reports the result.
HeadersExtra notes such as Content-Type or Accept.Extra notes such as Content-Type: application/json.
BodyOptional contents. GET and HEAD requests cannot have a body.Optional contents: JSON, HTML, text, a file, or nothing.
Status code families
FamilyMeaningCommon examples
1xxInformationalRare in everyday fetch code; the request is continuing.
2xxSuccess200 OK, 201 Created, 204 No Content
3xxRedirects and caching301, 302, 304. fetch follows redirects by default.
4xxClient-side problem400, 401, 403, 404, 409, 422, 429
5xxServer-side problem500, 502, 503

The Response object exposes that stamp through status, statusText, ok, headers, url, redirected, and type. statusText can be empty on modern HTTP versions, so do not build logic on the words.

fetch basics

CODE

The simplest shape is await fetch(url). It uses GET, follows redirects, and uses credentials: "same-origin" by default, which means same-origin cookies can go with same-origin requests. Request options, cache modes, and CORS get their own next lessons.

A careful fetch skeletonPop out in the code editor (opens in a new tab)JavaScript
async function carefulFetch(url) {  try {    const response = await fakeFetch(url);    console.log("resolved", response.ok, response.status);     if (response.status === 204) {      console.log("no body to parse");      return null;    }     const type = response.headers.get("content-type") || "";    if (!type.includes("application/json")) {      throw new Error("Expected JSON");    }     const data = await response.json();    if (!response.ok) {      throw new HttpError(response.status, data.message);    }    return data;  } catch (error) {    console.log("caught", error.name);    throw error;  }}

Notice the order. First await fakeFetch gets a Response. Then the code checks special statuses and headers. Then await response.json() reads the body. Finally, the code decides whether a non-OK HTTP status should become a thrown error for the caller.

Static lesson, real APIs

This course is statically exported and must not call external APIs. The playgrounds use a tiny in-page fake server that returns real Response objects, plus safe same-origin and data: requests your browser can perform.

response.ok and status

INTERACTIVE

response.ok is a convenience boolean: true for 200 through 299, false for everything else. It does not mean the JSON body is valid. It does not mean your app likes the data. It only summarizes the HTTP status range.

Status code switchboard
Careful fetch functionPop out in the code editor (opens in a new tab)JavaScript
async function carefulFetch(url) {  try {    const response = await fakeFetch(url);    console.log("resolved", response.ok, response.status);     if (response.status === 204) {      console.log("no body to parse");      return null;    }     const type = response.headers.get("content-type") || "";    if (!type.includes("application/json")) {      throw new Error("Expected JSON");    }     const data = await response.json();    if (!response.ok) {      throw new HttpError(response.status, data.message);    }    return data;  } catch (error) {    console.log("caught", error.name);    throw error;  }}
Run logok
  1. 1Choose a response and press Run. The same careful function handles every case.
Try it yourself

Every option uses real Response objects. Notice that 404 and 500 resolve, while the network option rejects before a Response exists.

A tiny in-page fake server returns real Response objects after a delay. It never contacts an external URL.

Try 404 and 500. The fetch promise resolves because the server replied. Your code chooses what to do next. A common pattern is to parse an error JSON body, then throw a custom custom error containing the status.

response.json()

STEP THROUGH

response.json() opens the envelope and reads the contents. It returns another Promise because the body may still be streaming. If the body is not valid JSON, that Promise rejects with SyntaxError. If the response has no body, such as 204, parsing also fails.

Real-life analogyjson opens the envelope

The Response is only the envelope. Calling json() opens it and reads the letter. That can fail separately from delivery: maybe the server sent HTML, an empty body, or broken JSON text.

In real life: Envelope arrives
In JavaScript: fetch resolves to Response
In real life: Open and read the pages
In JavaScript: response.json() reads the body stream
In real life: Unreadable handwriting
In JavaScript: Invalid JSON rejects with SyntaxError
In real life: Empty envelope
In JavaScript: 204 has no body to parse

Where the analogy stops: An envelope can be opened and reread on paper. A Response body is a stream, so the body methods consume it once.

Step through loadUser
Step 0 of 5Ready
Your turn: follow the blue line

Choose a server answer, then step through where the async function stops.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  const response = await fakeFetch("/api/users/" + id);   if (!response.ok) {    throw new HttpError(response.status, "User not found");  }   const user = await response.json();  return user.name;}
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the fake server answer

Changing the answer starts a fresh replay. The recording follows the same fake-server outcomes used in the switchboard.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Body can be read once
const response = new Response(JSON.stringify({ ok: true }), {  headers: { "Content-Type": "application/json" },}); const first = await response.json();console.log(first.ok); const second = await response.json();console.log(second.ok);
Body logtwice
  1. 1Run the demo to see bodyUsed change.
Try it yourself

A Response body is a stream. Once json(), text(), blob(), arrayBuffer(), or formData() consumes it, bodyUsed becomes true. Clone before reading if two consumers need it.

The demo uses a real Response object made in the page.

Other body methods follow the same one-use rule: text(), blob(), arrayBuffer(), and formData(). If two pieces of code need the body, call response.clone() before either reads it.

Real requests in the reader’s browser

REAL FETCH

Fake servers are useful for repeatable lessons, but fetch is a real browser API. This experiment runs only safe requests the static site can serve: this page, a missing same-origin page, a data: URL containing JSON, and a revoked blob URL that rejects without leaving your machine.

Real requests in your browser
Safe real requestsPop out in the code editor (opens in a new tab)JavaScript
await fetch(location.href);await fetch("/javascript/this-page-does-not-exist");await fetch('data:application/json,{"name":"Ada"}');const url = URL.createObjectURL(new Blob(["gone"]));URL.revokeObjectURL(url);await fetch(url);
Browser logreal fetch
  1. 1Press a button to run a real browser fetch.
Try it yourself

These are real fetch calls the static site can serve safely: this page, a same-origin 404, a data URL, and a revoked blob URL that rejects without leaving your machine.

No external URLs are used. Browser-specific error messages are not displayed; only the error name is shown.

The page request proves a response can be HTML instead of JSON. The missing path proves a same-origin 404 has ok: false. The data: request proves json() can parse JSON without an API server. The revoked blob URL proves a real network-style failure rejects with TypeError in the browser.

Network errors vs HTTP errors

SORT

The biggest fetch misconception is thinking every bad status rejects. It does not. A response with 404, 429, or 500 is still a response. Rejections are for “no usable response,” such as a network failure, invalid URL, browser CORS block, or abort. Aborts reject with the signal’s reason; many browser examples show an AbortError.

Rejects or resolves?
  • 404 JSON error response
  • 500 server error response
  • DNS failure or offline
  • CORS blocked by the browser
  • AbortController cancels it
  • 200 response containing { nope
Try it yourself
0 of 6 correct

Sort each outcome by which promise rejects.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

CORS is the next-but-one lesson. For now, remember the behavior: when the browser blocks a cross-origin response, your code does not get to inspect the status or body. fetch rejects with a TypeError.

Where you will use this

PRACTICAL

Fetching JSON appears anywhere a page needs fresh data: loading a profile, saving a todo, searching as the user types, refreshing a dashboard, or submitting a contact form. Reading JSON is common, but sending JSON is just as important.

Sending JSONPop out in the code editor (opens in a new tab)JavaScript
const todo = { title: "Learn fetch", done: false }; await fetch("/api/todos", {  method: "POST",  headers: { "Content-Type": "application/json" },  body: JSON.stringify(todo),});

A JSON request body is text. Use JSON.stringify(data) and set Content-Type: application/json so the server knows how to read it. Use POST, PUT, or PATCH for a body; GET requests cannot have one.

A reusable shape

Real apps often wrap fetch in a helper: await fetch, handle network rejection, check response.ok, skip 204, parse JSON only when expected, and throw a custom error with status and data.

Common misconceptions

WATCH OUT
  • “fetch rejects on 404.” It resolves with ok: false.
  • “response.json is the JSON.” It is a method that returns a Promise for parsed data.
  • “ok means my app is happy.” It only means the status is 200–299.
  • “Every response has JSON.” HTML pages, empty 204 replies, files, and invalid bodies exist.
  • “I can read the body over and over.” Body methods consume the stream once; use clone().
  • “Headers are case-sensitive.” Headers.get is case-insensitive, so content-type works.
Three promises people mix up
ThingResolves whenCommon rejection
fetch(url)Response headers arriveNetwork/CORS/invalid URL/abort
response.json()The body is read and parsedInvalid JSON, empty body, or body already used
Your helperYou return parsed dataWhatever errors you choose to throw, such as HttpError

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict ok and status

Type the two values printed by this Response.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const response = new Response("{}", { status: 404 });
console.log(response.ok);
console.log(response.status);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeSkip empty JSON

    Your helper crashes on 204 No Content. Which Response property should you check before parsing?

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the invalid JSON failure

      Predict the error name logged by the catch handler.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const response = new Response("not json", {
        headers: { "Content-Type": "application/json" },
      });
      response.json().catch((error) => console.log(error.name));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeRead the body twice safely

        What two values print after cloning the response?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const response = new Response(JSON.stringify({ ok: true }));
        const copy = response.clone();
        console.log((await response.json()).ok);
        console.log((await copy.json()).ok);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeSend JSON on purpose

          Which header tells the server the request body is JSON?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const todo = { title: "Learn fetch", done: false };
          
          await fetch("/api/todos", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(todo),
          });

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            7 QUESTIONS
            fetch & JSON quiz · 7 questionsScore: first tries count
            1. Question 1 of 7When does the promise returned by fetch resolve?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const response = new Response("{}", { status: 404 });
              console.log(response.ok);

              Choose an answer to see the explanation.

            3. Question 3 of 7What should you do before parsing a 204 response as JSON?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does invalid JSON do?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const response = new Response("not json");
              response.json().catch((error) => console.log(error.name));

              Choose an answer to see the explanation.

            5. Question 5 of 7Which request sends JSON correctly?

              Choose an answer to see the explanation.

            6. Question 6 of 7What happens if you call response.json twice on the same response?

              Choose an answer to see the explanation.

            7. Question 7 of 7Which default is true for browser fetch?

              Choose an answer to see the explanation.

            Key takeaways

            • fetch(resource, init) returns a Promise for a Response once headers arrive.
            • HTTP error statuses do not reject fetch. Check response.ok and response.status.
            • response.json() is a second await that consumes the body once and can reject.
            • Network failures, CORS failures, invalid URLs, and aborts reject the fetch promise before you can inspect a Response.
            • Send JSON with a non-GET method, Content-Type: application/json, and JSON.stringify(data).

            Final definition: fetch sends an HTTP request and resolves to a Response envelope; your code inspects the status, then reads the body with the right one-use body method.

            Up next: Requests in depth — methods, headers, bodies, and the Request and Response objects.

            CompleteFrontend Clear concepts. Working examples.