CodingNic

Asynchronous JavaScript

The Fetch API

Asynchronous JavaScript 25 min read

The Fetch API

Objectives

By the end of this chapter, you should be able to:

  • Request data from a server with fetch()
  • Read the response’s status and turn its body into usable data with .json()
  • Send data to a server with a POST request
  • Explain why fetch() doesn’t reject on an error status like 404

💡 Why this matters: Almost every real web page loads at least some of its data from a server after the page itself has loaded. fetch() is the built-in tool for that, and it’s a promise-based API, everything from this module applies directly.

A Real API to Practice On

Every example in this module uses JSONPlaceholder, a free, public practice API built specifically for this purpose, no signup, no API key. It has realistic endpoints like /users, /posts, and /todos, backed by real (if fake) data. GET requests return real data. POST, PUT, and DELETE requests are accepted and respond as if they worked, but nothing is actually saved, which is exactly what you want while learning: real request/response behavior, zero risk of breaking anything.

⚠️ A note on verification: every other example in this course was run and checked against real output. This sandbox’s network access is restricted to a small allowlist that doesn’t include JSONPlaceholder, so the fetch() calls themselves couldn’t be executed from here the way setTimeout(), promises, and everything else in this module were. The response data shown below is real, pulled directly from the live API, and fetch()’s mechanics (.json(), response.ok, sending a POST body) were fully verified against a real local test server earlier in this module, the exact same behavior a live API produces. Still, run these examples yourself in a browser console or Node to see it firsthand.

Making a Request

fetch(url) sends a GET request and returns a promise that resolves once the response’s headers have arrived (not necessarily the full body yet, that’s a separate step).

javascript
async function getUser() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  console.log(response.status);
  // 200

  console.log(response.ok);
  // true
}

getUser();

response.status is the HTTP status code. response.ok is a shortcut for “status is in the 200-299 range,” true for a successful response.

Reading the Response Body

The response’s body isn’t automatically parsed. .json() reads it and parses it as JSON, returning another promise.

javascript
async function getUser() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  const data = await response.json();
  console.log(data.name, data.email);
  // Leanne Graham Sincere@april.biz
}

getUser();

Two awaits here: one for the response itself to arrive, one for its body to be read and parsed. Both are asynchronous steps. The full object data holds a lot more than just name and email, a real user record includes an address, phone number, and company info too, this example only logs the two fields it needs.

Sending Data: POST

A plain fetch(url) call always sends GET. To send data, pass a second argument, an options object, with a method, headers, and a body.

javascript
async function createUser() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name: "Erin" }),
  });

  const data = await response.json();
  console.log(data);
  // { name: "Erin", id: 11 }
}

createUser();

body has to be a string, JSON.stringify() converts your object into one. The Content-Type: application/json header tells the server how to interpret that string. JSONPlaceholder responds with the object you sent plus a new id, as if it were really created, even though nothing was actually saved. This module doesn’t cover building the server side of this exchange, that’s Module 6, Web APIs and Backend Communication, this lesson focuses on the request itself.

The Gotcha: fetch() Doesn’t Reject on HTTP Errors

This trips up nearly everyone the first time. fetch()’s promise only rejects if the request itself fails to complete, no internet connection, the server unreachable, and so on. A 404 Not Found or a 500 Server Error is still a completed request, fetch() resolves normally, you just get a response with .ok set to false.

javascript
async function getMissingUser() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/9999");
  console.log(response.status);
  // 404
  console.log(response.ok);
  // false

  // No error was thrown. If you don't check response.ok,
  // this code would keep running as if everything succeeded.
}

getMissingUser();

JSONPlaceholder only has 10 users, ids 1 through 10, so /users/9999 is guaranteed not to exist. Always check response.ok (or response.status) before assuming a request succeeded. Handling this properly, along with the case where fetch() genuinely does reject, is the whole subject of next lesson.

Try It

  1. Write an async function that fetches "https://jsonplaceholder.typicode.com/todos/1", awaits .json() on the response, and logs data.title and data.completed.
  2. Inside that same function, log response.status and response.ok before parsing the body.
  3. Write an async function that sends a POST request to "https://jsonplaceholder.typicode.com/posts" with a JSON body of { title: "My First Post", body: "Hello", userId: 1 }, and logs the parsed response.
  4. Write an async function that fetches "https://jsonplaceholder.typicode.com/users/9999", checks response.ok, and logs "Request failed" instead of trying to parse the body, since this id doesn’t exist.

Recap

  • fetch(url) sends a GET request and resolves with a response once its headers arrive.
  • .json() reads and parses the response body, and is itself asynchronous, so it needs its own await.
  • Passing a second object to fetch() with method, headers, and a body sends data, JSON.stringify() turns an object into the string body requires.
  • fetch() only rejects on a genuine network failure. An HTTP error status like 404 still resolves normally, always check response.ok.

Next lesson: handling both kinds of failure, network errors and bad HTTP statuses, properly.