Fetch API & Asynchronous JavaScript (Promises, Async/Await)

From logging in users, loading products, submitting forms, to fetching live data, modern web applications constantly communicate with servers. In JavaScript, this communication happens using the Fetch API, Promises, and async/await. 


However, many beginners get confused when working with asynchronous code. API calls return pending results, <.then()> chains become messy, and errors are hard to debug. 


In this blog, I will explain: 


  • Fetch in JavaScript

  • Async and await

  • Promises

  • How to handle errors correctly?

  • How to write clean code using the Fetch API?

What is the Fetch API in JavaScript?

The Fetch API is a modern, built-in JavaScript feature used to send HTTP requests and receive data from servers. In simple words, it’s how your frontend talks to a backend. Whenever you load user data, submit a form, call a payment API, fetch products from a database, or connect to any third-party servers, you are making an API request. 


And today, the cleanest way to do that in fetch in Java Script is the fetch() function. Before Fetch, developers used XMLHTTPRequest (XHR), which was verbose, messy, and harder to read. Fetch makes the same work simpler, cleaner, and promise-based by default. 


The basic syntax is straightforward: 

fetch(url, options)


Here, the URL is your API endpoint, and options contain things like method, headers, or request body if needed. 


Here is a simple example where we fetch users from an API: 

fetch("https://jsonplaceholder.typicode.com/users")

  .then(response => response.json())

  .then(data => console.log(data))

  .catch(error => console.error(error));


When this runs, fetch sends a request to the server, waits for the response, converts it into JSON, and then gives you the final date. Because servers take time to respond, fetch does not block the program. Instead, it works asynchronously using promises in the background. 

Understanding Promises in JavaScript

Before learning async and await in JavaScript, you must clearly understand Promises, because both fetch in JavaScript and async/await are built on top of them. If Promises feel confusing, everything else will feel confusing, too. 


In JavaScript, a Promise is simply an object that represents a value that will be available in the future, not immediately. 


JavaScript works the same way when calling APIs. The server needs time to respond, so instead of blocking the whole program, JavaScript continues running and gives you a Promise that says, “I will give you the results when it’s ready.” 


A Promise always has three states: 


  • Pending: Still waiting for the result

  • Fulfilled: Success, data retrieved

  • Rejected: Failed, error occurred


Promises are handled using a few common methods: 


  • .then(): Runs when the task succeeds

  • .catch(): Runs when something fails

  • .finally(): Runs no matter what


Here’s a Promise example without Fetch: 

const myPromise = new Promise((resolve, reject) => {

  let success = true;


  if (success) {

    resolve("Data received");

  } else {

    reject("Something went wrong");

  }

});


myPromise

  .then(result => console.log(result))

  .catch(error => console.error(error));


Now, let’s connect this with fetch, because fetch actually returns a Promise: 


fetch("https://jsonplaceholder.typicode.com/posts/1")

  .then(response => response.json())

  .then(data => console.log(data))

  .catch(error => console.error(error));


Notice the flow:

 

  • Fetch sends request

  • It returns a Promise

  • .then() waits for response

  • Next .then() handles data

  • .catch() handles errors


This works fine, but when you chain too many .then() calls, the code becomes messy and hard to read. Many beginners call this “callback hell 2.0.”

What is Async/Await in JavaScript?

If Promises make asynchronous code possible, async/await makes it readable. Most beginners understand .then() at first, but once there are multiple API calls, nested logic, or error handling, the code quickly becomes difficult to maintain. Long promise chains feel confusing, especially when debugging. 


That’s exactly why modern JavaScript introduced async await in JavaScript. It is a cleaner syntax that lets you write asynchronous code that looks synchronous. 


In simple words, async/await is just a better way to work with Promises. It helps you: 


  • Write cleaner and more readable code

  • Avoid long .then() chains

  • Handle errors easily with try and catch

  • Debug faster like normal step-by-step code

  • Make production code look professional


  • The async keyword tells JavaScript: This function will work asynchronously and return a Promise

  • The await keyword tells JavaScript: Pause here until the Promise finishes, then give me the result.


Because of this, your code executes in a top-to-bottom, natural flow, which feels much easier to understand. Let’s check out the Promise style: 


fetch("https://jsonplaceholder.typicode.com/users")

  .then(response => response.json())

  .then(data => console.log(data))

  .catch(error => console.error(error));


Now the same code using JavaScript async await: 


async function getUsers() {

  try {

    const response = await fetch("https://jsonplaceholder.typicode.com/users");

    const data = await response.json();

    console.log(data);

  } catch (error) {

    console.error(error);

  }

}


getUsers();


Notice the difference between the two. No chaining, no mental gymnastics. Just clean, step-by-step logic like normal code. 


That’s why most modern developers prefer async await instead of promises for day-to-day development. Under the hood, it still uses Promises, but the syntax is much easier for humans to read and maintain. 

Promises vs Async Await (Side-by-Side Comparison)

You have seen both async/await and Promises solve handling asynchronous problems in JavaScript. Still, understanding the difference between promise and async await helps you write better, cleaner code and answer interview questions confidently. 


Feature

Promises (.then/.catch)

Async/Await

Syntax style

Chain-based

Looks like normal synchronous code

Readability

Can get messy with many .then()

Very clean and linear

Beginner friendly

Medium (confusing at first)

Easy to understand

Error handling

.catch() blocks

try...catch (simpler)

Debugging

Harder (nested flow)

Easier (step-by-step)

Code length

Usually longer

Shorter

Multiple API calls

Becomes chained and complex

Much cleaner

Under the hood

Native Promise

Still uses Promise

Interview expectation

Must know

Preferred in modern apps

Real-world usage

Discover More Courses on Skillwaala