LCWD LogoLearn Code With DurgeshLCWD
HomeCoursesHandbooksBlogsContact
About LCWDAbout Durgesh Tiwari
Flex Box Hero
LearnCodeWithDurgesh Logo

Learn Code With Durgesh

Offering free & premium coding courses to lakhs of students via YouTube and our platform.

Explore

  • About Us
  • Courses
  • Blog
  • Contact
  • FlexBox Game

Legal

  • Privacy Policy
  • Terms & Conditions
  • Refund Policy
  • Support

Contact

  • 📞 +91-9839466732
  • [email protected]
  • Substring Technologies, 633/D/P256 B R Dubey Enclave Dhanwa Deva Road Matiyari Chinhat, Lucknow, UP, INDIA 226028
© 2025 Made with ❤️ by Substring Technologies. All rights reserved.
Async/Await vs Promises in JavaScript: Which Should You Use?

Async/Await vs Promises in JavaScript: Which Should You Use?

By Shruti • Tue Sep 02 2025

Async/Await vs Promises in JavaScript: Which Should You Use?

Async/Await vs Promises in JavaScript: A Complete Guide

Asynchronous programming is one of the most important topics in modern JavaScript development. Whether you’re building a web app, mobile application, or integrating APIs, you’ll often deal with operations that don’t finish instantly, such as fetching data or reading files.

This is where Promises and Async/Await come into play. In this guide, we’ll dive deep into Promises vs Async/Await, explore how they work, compare their syntax, look at real-world examples, and discuss when to use each.


Introduction

Why Asynchronous Programming Matters in JavaScript

JavaScript runs on a single thread, meaning one task executes at a time. If you block this thread with a long-running task (like fetching API data), the application freezes until it’s done.

Asynchronous programming allows JavaScript to:

  • Perform multiple tasks without blocking.
  • Keep the UI smooth and responsive.
  • Handle heavy tasks like API calls and file I/O efficiently.

Example:

Imagine a weather app fetching live data. Without async code, the app would hang until the response arrives. With Promises or Async/Await, the app remains interactive while the data loads in the background.


The Problem with Callbacks

Before Promises, JavaScript developers relied heavily on callbacks to handle asynchronous operations.

A callback is simply a function passed as an argument to another function, which gets executed once the task is completed.

Example: Basic Callback

function fetchData(callback) {
  setTimeout(() => {
    callback("Data received!");
  }, 2000);
}

fetchData(function(result) {
  console.log(result);
});

👉 Here, fetchData waits 2 seconds, then runs the callback function to log "Data received!".


Callback Hell

Callbacks are fine for simple tasks, but they quickly become unmanageable when multiple asynchronous operations depend on each other. This leads to “callback hell”—deeply nested, pyramid-shaped code that’s difficult to read and maintain.

Example: Callback Hell

getUser(function(user) {
  getPosts(user.id, function(posts) {
    getComments(posts[0].id, function(comments) {
      getLikes(comments[0].id, function(likes) {
        console.log("Likes:", likes);
      });
    });
  });
});

Here’s what happens:

  1. getUser() is called.
  2. With that result, getPosts() is called.
  3. Then getComments().
  4. Finally, getLikes().

What Are Promises in JavaScript?

A Promise is an object that represents the eventual completion (or failure) of an asynchronous task. Instead of returning a value right away, it gives a placeholder that will be filled later.

  • Helps manage asynchronous tasks.
  • Makes code more structured compared to callbacks.
  • Provides .then() and .catch() for handling results and errors.

It can be in one of the following three states:

Pending

  • This is the initial state of a Promise.
  • It means the asynchronous operation has started but is not yet finished.
  • At this stage, the Promise has no final value (neither success nor failure).
  • Example: waiting for data from an API call.
const promise = new Promise((resolve, reject) => {
  // still waiting, nothing resolved or rejected yet
});
console.log(promise); // Promise { <pending> }

Fulfilled (Resolved)

  • When the asynchronous task completes successfully, the Promise changes from pending → fulfilled.
  • A value (the result of the operation) is returned and passed to the .then() handler.
  • Example: API data successfully retrieved.
const promise = Promise.resolve("Data loaded!");
promise.then(result => console.log(result)); // Output: Data loaded!

Rejected

  • When the asynchronous task fails (due to an error, network issue, or invalid input), the Promise moves from pending → rejected.
  • An error reason is provided, which can be handled using .catch().
  • Example: failed API request.
const promise = Promise.reject("Network error!");
promise.catch(error => console.error(error)); // Output: Network error!

Flowchart: Promise States

   ┌─────────┐
   │ Pending │
   └────┬────┘
        │
   ┌────▼────┐         ┌──────────┐
   │ Fulfilled│  OR →   │ Rejected │
   └─────────┘         └──────────┘

image

Example: Basic Promise

let myPromise = new Promise((resolve, reject) => {
  let isSuccess = true;
  if (isSuccess) {
    resolve("Task completed successfully!");
  } else {
    reject("Something went wrong!");
  }
});

myPromise
  .then(result => console.log(result))
  .catch(error => console.error(error));

Chaining with .then() and .catch()

One of the biggest advantages of Promises is that they allow you to chain multiple asynchronous operations together in a clean, linear way.

1. .then()

  • Used to handle the successful result of a Promise.
  • Each .then() returns a new Promise, allowing chaining.
  • The value returned in one .then() can be passed to the next .then().
fetch("<https://jsonplaceholder.typicode.com/posts/1>")
  .then(res => res.json())            // first then → convert response to JSON
  .then(data => console.log(data))    // second then → access parsed data

2. .catch()

  • Used to handle errors if a Promise is rejected.
  • Can be placed at the end of a chain to catch errors from any step.
fetch("<https://jsonplaceholder.typicode.com/invalid-url>")
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(error => console.error("Something went wrong:", error));

3. Chaining Example

Here’s a full example with multiple .then() calls and one .catch():

fetch("<https://jsonplaceholder.typicode.com/users/1>")
  .then(res => res.json())                // step 1: parse user
  .then(user => fetch(`/posts?userId=${user.id}`))   // step 2: fetch posts
  .then(res => res.json())                // step 3: parse posts
  .then(posts => console.log("Posts:", posts))       // step 4: log posts
  .catch(err => console.error("Error:", err));       // handle any errors

What Is Async/Await in JavaScript?

Async/Await, introduced in ES2017 (ES8), is a feature built on top of Promises.

It doesn’t replace Promises but provides a cleaner, more readable syntax for working with them.

Think of it as writing asynchronous code that looks synchronous.


Syntactic Sugar over Promises

  • An async function always returns a Promise, even if you don’t explicitly return one.
  • Inside an async function, you can use the await keyword, which pauses execution until the Promise resolves.
  • This helps avoid chaining .then() calls, making code easier to read.

How Async Functions Work

async function example() {
  return "Hello!";
}
example().then(msg => console.log(msg)); // Outputs: Hello!

Explanation:

  • The function example is declared with async.
  • Even though it returns a simple string "Hello!", JavaScript wraps it in a Promise.
  • So calling example() actually returns a Promise, which is why we can use .then() to get the value.

Using await for Cleaner Code

async function getData() {
  const response = await fetch("<https://jsonplaceholder.typicode.com/users/1>");
  const user = await response.json();
  console.log("User:", user.name);
}
getData();

Explanation:

  • The await keyword tells JavaScript: “wait here until this Promise is resolved.”
  • fetch(...) returns a Promise → execution pauses until the response arrives.
  • response.json() also returns a Promise → execution pauses again until data is parsed.
  • The code looks like normal synchronous code, but it’s actually asynchronous under the hood.

👉 This makes the code much easier to read and debug compared to .then() chaining.


Example: Converting a Promise Chain into Async/Await

Using Promises (chained with .then())

fetch("<https://jsonplaceholder.typicode.com/users/1>")
  .then(res => res.json())
  .then(user => console.log(user.name))
  .catch(err => console.error(err));
  • Each .then() handles the next step.
  • Works fine, but for longer chains, it can get messy.

Using Async/Await (cleaner)

async function fetchUser() {
  try {
    const res = await fetch("<https://jsonplaceholder.typicode.com/users/1>");
    const user = await res.json();
    console.log(user.name);
  } catch (err) {
    console.error(err);
  }
}
fetchUser();
  • The same logic, but easier to read.
  • try...catch is used instead of .catch() for error handling, which feels more natural.
  • No chaining required → looks like step-by-step synchronous code.

Promises vs Async/Await: Key Differences

FeaturePromisesAsync/Await
Syntax.then(), .catch()try...catch with await
ReadabilityNested chains can get messyLooks synchronous & clean
Error Handling.catch()try...catch
DebuggingStack traces harderCleaner stack traces
Parallel ExecutionPromise.all()await Promise.all()

Example: Parallel Execution

// Sequential (slower)
await task1();
await task2();

// Parallel (faster)
await Promise.all([task1(), task2()]);

Code Examples

Fetching Data with Promises

One of the most common uses of Promises in JavaScript is fetching data from an API.

The fetch() function, built into modern browsers, returns a Promise that resolves to a Response object.

  • First, fetch() starts the HTTP request and immediately returns a Promise in the pending state.
  • When the response arrives successfully, the Promise becomes fulfilled, and you can handle it with .then().
  • If there’s a network error, the Promise becomes rejected, which you can handle with .catch().

Example: Fetching Data with Promises

fetch("<https://jsonplaceholder.typicode.com/todos/1>")
  .then(response => response.json())         // convert response to JSON
  .then(data => console.log("Todo:", data))  // use the parsed data
  .catch(error => console.error("Error:", error));

How this works step by step:

  1. fetch(...) → starts the request, returns a Promise.
  2. .then(response => response.json()) → when fulfilled, converts the response into JSON (which itself returns another Promise).
  3. .then(data => console.log(data)) → logs the final data after JSON parsing.
  4. .catch(error => ...) → handles errors (like network failure).

Same Task with Async/Await

Earlier, we saw how to fetch data with Promises using .then() and .catch().

Now, we’ll do the same task with Async/Await, which makes the code look cleaner and easier to follow.


Using Promises

fetch("<https://jsonplaceholder.typicode.com/todos/1>")
  .then(response => response.json())
  .then(todo => console.log("Todo:", todo))
  .catch(error => console.error("Error:", error));
  • Each .then() handles the next step.
  • Works fine, but as steps increase, the chain grows longer and harder to read.

Same Task with Async/Await

async function fetchTodo() {
  try {
    const res = await fetch("<https://jsonplaceholder.typicode.com/todos/1>");
    const todo = await res.json();
    console.log("Todo:", todo);
  } catch (err) {
    console.error("Error:", err);
  }
}
fetchTodo();

Explanation

  1. Async Function
    • The function is declared with async, so it always returns a Promise.
  2. Await Fetch
    • await fetch(...) pauses execution until the HTTP response is received.
  3. Await JSON Parsing
    • await res.json() waits until the response body is fully read and converted into a JavaScript object.
  4. Console Output
    • The result (todo) is printed directly.
  5. Error Handling with Try/Catch
    • Any error in fetching or parsing is caught in the catch block.

Error Handling Comparison

Error handling is an important part of asynchronous programming. Both Promises and Async/Await provide ways to deal with errors, but the style is slightly different.


With Promises

  • Errors are handled using .catch().
  • If any step in the chain fails (network error, parsing error, etc.), it jumps into .catch().
fetch("<https://jsonplaceholder.typicode.com/invalid-url>")
  .then(response => response.json())
  .then(data => console.log("Data:", data))
  .catch(error => console.error("Promise Error:", error));

👉 Here, .catch() will catch:

  • Network failures
  • Invalid JSON parsing
  • Or anything thrown in previous .then()

With Async/Await

  • Errors are handled using try...catch.
  • If any awaited Promise rejects, execution jumps to the catch block.
async function fetchData() {
  try {
    const res = await fetch("<https://jsonplaceholder.typicode.com/invalid-url>");
    const data = await res.json();
    console.log("Data:", data);
  } catch (error) {
    console.error("Async/Await Error:", error);
  }
}
fetchData();

👉 Here, try...catch makes error handling look like synchronous code.


When to Use Promises

  • For concurrent tasks.
  • When using libraries already promise-based.
  • For lightweight chaining of async tasks.

When to Use Async/Await

  • For sequential logic.
  • To achieve readable synchronous-like flow.
  • When you want easier debugging and error handling.

Performance Considerations

Do Async/Await Make Code Faster?

No. Both Promises and Async/Await run on the same engine mechanics. Async/Await is not faster—it’s just more readable.

Promise.all with Async/Await for Optimization

Promise.all with Async/Await allows you to run multiple asynchronous tasks in parallel instead of waiting for each one separately. This improves performance by fetching or processing data simultaneously, and await Promise.all([...]) waits until all promises are resolved (or rejected).

async function getUserAndPosts() {
  const [userRes, postsRes] = await Promise.all([
    fetch("<https://jsonplaceholder.typicode.com/users/1>"),
    fetch("<https://jsonplaceholder.typicode.com/posts?userId=1>")
  ]);

  const user = await userRes.json();
  const posts = await postsRes.json();

  console.log("User:", user);
  console.log("Posts:", posts);
}
getUserAndPosts();

Common Mistakes to Avoid

  1. Forgetting try...catch in async/await.
async function badExample() {
  const res = await fetch("invalid-url"); // crashes without try/catch
}
  1. Mixing .then() and await unnecessarily.
// Wrong
await fetch(url).then(res => res.json());

// Correct
const res = await fetch(url);
const data = await res.json();
  1. Blocking code with multiple awaits in a loop.
// Wrong: Slow
for (let url of urls) {
  const res = await fetch(url);
}

// Correct: Fast
await Promise.all(urls.map(url => fetch(url)));

Conclusion

Both Promises and Async/Await are essential for handling asynchronous code in JavaScript.

  • Promises → Great for concurrency and chaining.
  • Async/Await → Ideal for readable, step-by-step async code.

👉 The best approach is often to combine them depending on the situation.


FAQs

Q1. Can we use async/await without Promises?

No. Async/Await is built on top of Promises.

Q2. Is async/await better than Promises?

Not faster—just cleaner. Promises are still very important.

Q3. Does async/await replace callbacks?

Yes, in modern JavaScript, async/await helps you avoid callback hell.

Share this article ...

💬WhatsApp📘Facebook💼LinkedIn🐦X

Trending Blogs...

iphone 17 Launch 2025: Features, Price in India, Pros & Cons

iphone 17 Launch 2025: Features, Price in India, Pros & Cons

Apple has officially unveiled the **iPhone 17 series** on **September 9, 2025**, at its **“Awe Dropping” event**. The lineup includes **iPhone 17, iPhone 17 Air, iPhone 17 Pro, and iPhone 17 Pro Max**. With **120 Hz displays across all models, ultra-thin design, A19 chips, and camera upgrades**, Apple is raising the bar again.

Async/Await vs Promises in JavaScript: Which Should You Use?

Async/Await vs Promises in JavaScript: Which Should You Use?

Asynchronous programming is one of the most important topics in modern JavaScript development. Whether you’re building a web app, mobile application, or integrating APIs, you’ll often deal with operations that don’t finish instantly, such as fetching data or reading files.

AI: Boon or Curse? Advantages & Disadvantages Explained

AI: Boon or Curse? Advantages & Disadvantages Explained

Artificial Intelligence (AI) is no longer just a concept of the future—it is already shaping our present. From asking Alexa or Siri to play music, using Google Maps for directions, or experiencing personalized recommendations on Netflix, AI has become a part of our everyday life.

The Future of Technology: Quantum Computing and Its Ecosystem

The Future of Technology: Quantum Computing and Its Ecosystem

Quantum computing isn’t just another buzzword—it’s a completely new way of thinking about computers. Unlike our everyday laptops or phones that run on classical computing, quantum computers use the strange but powerful rules of quantum mechanics.

Tailwind CSS Cheat Sheet – Complete Utility Class Guide 2025

Tailwind CSS Cheat Sheet – Complete Utility Class Guide 2025

Tailwind CSS Cheat Sheet – Complete Utility Class Guide 2025

Expert-Level JavaScript Interview Q&A for 2025: Crack Advanced JS Interviews

Expert-Level JavaScript Interview Q&A for 2025: Crack Advanced JS Interviews

Get ready for tough coding interviews with this collection of advanced JavaScript interview questions and answers. Designed for experienced developers who want to master complex JS concepts and impress recruiters.

Share this article ...

💬WhatsApp📘Facebook💼LinkedIn🐦X