Back to blog
·3 min read·Node.js / Asynchronous Programming / Backend

Promise vs Async/Await

Understanding Promises and Async/Await in JavaScript with examples.

Promise vs Async/Await

In JavaScript, Promise and Async/Await are two methods used to handle asynchronous operations. Both solve the same problem but follow different approaches.

Synchronous Programming

+-------------------+       +---------------------+       +-------------------+
|   Task 1 Starts   | ----> |  Task 1 Completes   | ----> |   Task 2 Starts   |
+-------------------+       +---------------------+       +-------------------+

Asynchronous Programming

+-------------------+       +-------------------+
|   Task 1 Starts   | ----> |   Task 2 Starts   |
+-------------------+       +-------------------+
        |                           |
        v                           v
+---------------------+     +---------------------+
|  Task 1 Completes   |     |  Task 2 Completes   |
+---------------------+     +---------------------+

What is Asynchronous Programming?

Unlike synchronous programming, where each task must complete before the next one starts, asynchronous programming allows multiple tasks to run independently.

This helps JavaScript remain responsive while waiting for operations like:

  • API requests
  • Database calls
  • File operations
  • Timers

In asynchronous programming:

  • The program does not wait for a task to finish
  • It continues executing other tasks
  • Results are handled later when available

1) Promise

A Promise in JavaScript represents a value that may be available:

  • now
  • in the future
  • or never

Promises are used to handle future asynchronous results.

Promise States

A Promise has 3 states:

  • Pending → Initial state
  • Fulfilled / Resolved → Operation completed successfully
  • Rejected → Operation failed

Initially, every Promise starts in the pending state.


Handling a Promise

Promises are handled using:

  • .then() → Handles successful resolution
  • .catch() → Handles errors or rejection

Promise Example

Setup

mkdir promises
cd promises
npm init -y
npm i node-fetch

app.js

const fetch = require("node-fetch");

function apiFetch() {
  const data = new Promise((resolve, reject) => {
    fetch("<URL>", {
      method: "GET",
    })
      .then((res) => res.json())
      .then((data) => resolve(data))
      .catch((err) => reject(err));
  });

  data
    .then((res) => console.log(res))
    .catch((err) => console.log(err));
}

console.log("Fetching...");
apiFetch();
console.log("Fetched...");

Output

Fetching...
Fetched...
[{ <data> }]

You may notice that "Fetched..." appears before the API response.

This happens because Promises are non-blocking. JavaScript continues executing the remaining code while the API request runs in the background.


Code Explanation

Creating a Promise

new Promise((resolve, reject) => {})
  • resolve() → Marks the Promise as successful
  • reject() → Marks the Promise as failed

fetch() Returns a Promise

fetch("<URL>")

The fetch() method itself returns a Promise.


Why Another .then()?

.then((res) => res.json())

res.json() also returns a Promise.

So we use another .then() to resolve the JSON data.


Important Note

Without handling the Promise properly, you may see:

Promise { <pending> }

instead of actual data.


Simple Analogy

Think of a Promise like ordering food at a restaurant.

  • You place the order
  • The kitchen prepares it
  • Meanwhile, you continue talking with friends
  • Once ready, the food is delivered

Similarly:

  • resolve() → Food delivered successfully
  • reject() → Something went wrong

2) Async/Await

async/await is another way to handle asynchronous operations.

It is built on top of Promises and provides cleaner, more readable syntax.

Key Points

  • An async function always returns a Promise
  • await pauses execution until the Promise resolves
  • Makes asynchronous code look synchronous

Async/Await Example

async function fetchData() {
  try {
    console.log("Fetching...");

    const data = await fetch("<URL>", {
      method: "GET",
    });

    const res = await data.json();

    console.log(res);
    console.log("Fetched...");
  } catch (error) {
    console.log(error);
  }
}

fetchData();

Output

Fetching...
[{ <data> }]
Fetched...

Explanation

async Function

async function fetchData() {}

This tells JavaScript the function contains asynchronous operations.


await Keyword

const data = await fetch(...)

await pauses execution until the Promise resolves.

Unlike standard Promise chaining, the code waits here before moving to the next line.


Why await data.json()?

const res = await data.json();

Because data.json() also returns a Promise.

Using await resolves it into actual JSON data.


Error Handling

Always use try...catch with async/await:

try {
  // async code
} catch (error) {
  console.log(error);
}

This helps handle rejected Promises cleanly.


Promise vs Async/Await

PromiseAsync/Await
Uses .then() and .catch()Uses async and await
Chain-based syntaxCleaner syntax
Can become harder to readEasier to understand
Non-blocking flowLooks synchronous
Better for complex chainingBetter for readability

Conclusion

Both Promises and Async/Await are used to handle asynchronous operations in JavaScript.

Promise Analogy

A Promise is like receiving a restaurant token:

  • You continue doing other things
  • Later, your food arrives

Async/Await Analogy

Async/Await is like cooking yourself:

  • You wait until cooking finishes
  • Then continue with the next step

Both approaches achieve the same goal, but async/await usually provides cleaner and more maintainable code.