Skip to main content

Command Palette

Search for a command to run...

Using the Fetch API with Promises in JavaScript

Updated
4 min read

Modern JavaScript applications constantly talk to servers—fetching data, sending form submissions, or updating resources. The Fetch API is the standard way to perform HTTP requests in the browser, and it is promise-based by design.

This article explains:

  • What fetch() is

  • How promises work with fetch

  • Handling JSON responses

  • Error handling (the right way)

  • Common real-world patterns

  • Mistakes to avoid


1. What is the Fetch API?

fetch() is a browser API used to make HTTP requests such as:

  • GET (read data)

  • POST (send data)

  • PUT / PATCH (update data)

  • DELETE (remove data)

It returns a Promise that resolves to a Response object.

fetch(url)

Important:

  • fetch() does not return the data directly

  • It returns a Promise

  • That Promise resolves when the HTTP response arrives


2. Basic Fetch Example (GET request)

fetch("https://api.example.com/users")
  .then((response) => {
    return response.json();
  })
  .then((data) => {
    console.log(data);
  });

What happens step by step?

  1. fetch() sends an HTTP request

  2. It returns a Promise

  3. The first .then() receives a Response object

  4. response.json() reads the body and returns another Promise

  5. The second .then() receives the parsed JSON data


3. Why Does response.json() Return a Promise?

Because reading the response body is asynchronous.

The response arrives as a stream, and JavaScript needs time to:

  • Read it

  • Decode it

  • Parse JSON

That’s why this is wrong:

const data = response.json(); // ❌ data is a Promise

And this is correct:

response.json().then((data) => {
  console.log(data);
});

4. Handling HTTP Errors Properly

⚠️ Important rule
fetch() only rejects on network errors, not HTTP errors like 404 or 500.

❌ Wrong assumption

fetch(url)
  .catch(() => {
    // This will NOT run for 404 or 500
  });

✅ Correct error handling pattern

fetch("https://api.example.com/users")
  .then((response) => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error("Fetch failed:", error.message);
  });

Why this works

  • response.ok is true for status codes 200–299

  • You manually throw an error

  • Thrown errors are caught by .catch()


5. Making a POST Request with Fetch

Sending data to a server requires:

  • HTTP method

  • Headers

  • Request body

fetch("https://api.example.com/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Pushpesh",
    role: "Frontend Developer",
  }),
})
  .then((response) => {
    if (!response.ok) {
      throw new Error("Failed to create user");
    }
    return response.json();
  })
  .then((data) => {
    console.log("User created:", data);
  })
  .catch((error) => {
    console.error(error.message);
  });

6. Chaining Multiple .then() Calls

Promises allow step-by-step transformations.

fetch("/api/profile")
  .then((res) => res.json())
  .then((user) => user.name)
  .then((name) => name.toUpperCase())
  .then((finalName) => {
    console.log(finalName);
  });

Each .then():

  • Receives the result of the previous one

  • Can transform and return a new value


7. Common Fetch Mistakes

❌ Forgetting to return response.json()

fetch(url)
  .then((response) => {
    response.json(); // ❌ missing return
  })
  .then((data) => {
    console.log(data); // undefined
  });

✅ Correct

.then((response) => {
  return response.json();
});

❌ Assuming fetch throws on 404

fetch("/wrong-url")
  .then((res) => res.json())
  .catch(() => console.log("Error")); // ❌

✅ Correct

.then((res) => {
  if (!res.ok) throw new Error("Not found");
  return res.json();
});

8. Fetch vs XMLHttpRequest (Old Way)

FeaturefetchXMLHttpRequest
Promise-based
Cleaner syntax
Stream support
Modern standard

9. When to Use Promises vs async/await?

fetch() always uses Promises internally.

This article focused on Promises directly, but most production code today uses async/await, which is just syntax sugar on top of Promises.

Understanding promise-based fetch:

  • Makes debugging easier

  • Helps you read older code

  • Strengthens your JavaScript fundamentals


10. Mental Model to Remember

Think of fetch() like this:

“I am asking the browser to make a request.
It promises to tell me later when the response arrives.”

And each .then() is:

“When you’re done, do this next.”


Final Takeaways

  • fetch() returns a Promise

  • response.json() returns another Promise

  • HTTP errors must be handled manually

  • Always return promises in .then()

  • Promise chaining allows clean, readable async flows