DEV Community

BysonTech
BysonTech

Posted on

Why JavaScript Returns a Promise: Understanding async / await from the Ground Up

If you have ever written async JavaScript and wondered:

  • Why is this returning a Promise?
  • Why can’t I get the value immediately?
  • What exactly is async / await doing?

you are not alone.

Many developers use Promise and async / await without fully understanding what is happening under the hood. I was the same way at first.

This article breaks it down in a simple, practical way.


1. What Synchronous Code Means

JavaScript runs code from top to bottom, in order.

This is called synchronous execution.

Example:

console.log("A");
console.log("B");
console.log("C");
Enter fullscreen mode Exit fullscreen mode

Output:

A  
B  
C  
Enter fullscreen mode Exit fullscreen mode

2. Why Asynchronous Code Exists

Some operations take time:

  • API calls
  • File reading
  • Database access
  • User input
  • Timers

If these were all synchronous, the UI would freeze.

So JavaScript allows code to continue running without waiting for these operations to finish.

That is asynchronous processing.

Example:

console.log("Start");

setTimeout(() => {
  console.log("After 3 seconds");
}, 3000);

console.log("End");
Enter fullscreen mode Exit fullscreen mode

Output:

Start  
End  
After 3 seconds  
Enter fullscreen mode Exit fullscreen mode

The callback runs later, so "End" appears first.


3. What a Promise Actually Is

A Promise solves one core problem:

How do we handle a result that is not available yet?

A Promise is:

a value that will be available in the future

You can think of it as a placeholder.


Promise States

A Promise has three states:

  • pending → still running
  • fulfilled → success
  • rejected → failure

When the state changes, then() or catch() runs.


Example

function task() {
  return new Promise((resolve, reject) => {
    const success = true;

    if (success) {
      resolve("Success");
    } else {
      reject("Failure");
    }
  });
}

task()
  .then((result) => console.log(result))
  .catch((error) => console.log(error));
Enter fullscreen mode Exit fullscreen mode

4. Why You Don’t Get the Value Immediately

This is the most important point.

function getData() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("OK"), 2000);
  });
}

const data = getData();
console.log(data);
Enter fullscreen mode Exit fullscreen mode

Output:

Promise { <pending> }
Enter fullscreen mode Exit fullscreen mode

The value is not ready yet.

A Promise is not the value — it is a future value.


5. Promise Chaining

You can chain asynchronous steps:

getData()
  .then((data) => {
    console.log(data);
    return "Next step";
  })
  .then((msg) => console.log(msg))
  .catch((error) => console.log(error));
Enter fullscreen mode Exit fullscreen mode

This works, but can become hard to read.

That is why async / await exists.


6. What async Means

async means:

this function always returns a Promise

Example:

async function test() {
  return "hello";
}
Enter fullscreen mode Exit fullscreen mode

This is the same as:

Promise.resolve("hello")
Enter fullscreen mode Exit fullscreen mode

7. What await Means

await means:

wait for a Promise and return its value

Example:

function getData() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("Data"), 2000);
  });
}

async function main() {
  const data = await getData();
  console.log(data);
}

main();
Enter fullscreen mode Exit fullscreen mode

await pauses the function, not the whole program.


8. Promise vs async / await

Promise:

getData()
  .then((data) => console.log(data))
  .catch((error) => console.log(error));

async / await:

async function main() {
  try {
    const data = await getData();
    console.log(data);
  } catch (error) {
    console.log(error);
  }
}
Enter fullscreen mode Exit fullscreen mode

async / await is just a cleaner way to write Promise-based code.


9. Error Handling

With Promise → catch()

With async → try / catch

async function main() {
  try {
    const data = await getData();
  } catch (error) {
    console.log(error);
  }
}
Enter fullscreen mode Exit fullscreen mode

10. Parallel Processing

Sequential:

const a = await fetchA();
const b = await fetchB();
Enter fullscreen mode Exit fullscreen mode

Parallel:

const [a, b] = await Promise.all([
  fetchA(),
  fetchB()
]);
Enter fullscreen mode Exit fullscreen mode

11. Promise.allSettled

If you need all results:

const results = await Promise.allSettled([
  task1(),
  task2()
]);
Enter fullscreen mode Exit fullscreen mode

12. fetch with async / await

async function getUsers() {
  const response = await fetch("/api/users");

  if (!response.ok) {
    throw new Error("Request failed");
  }

  const data = await response.json();

  return data;
}
Enter fullscreen mode Exit fullscreen mode

Two steps:

  1. Wait for response
  2. Wait for JSON parsing

13. Common Mistakes

async returns a Promise

async function test() {
  return 123;
}

const x = test(); // Promise
Enter fullscreen mode Exit fullscreen mode

await outside async

function main() {
  const x = await test(); // Error
}
Enter fullscreen mode Exit fullscreen mode

Missing error handling

async function main() {
  const data = await getData(); // may crash
}
Enter fullscreen mode Exit fullscreen mode

14. When to Use new Promise

Usually, you do not need it.

Bad:

function bad() {
  return new Promise((resolve, reject) => {
    fetch("/api/data")
      .then((res) => res.json())
      .then(resolve)
      .catch(reject);
  });
}
Enter fullscreen mode Exit fullscreen mode

Good:

function good() {
  return fetch("/api/data")
    .then((res) => res.json());
}
Enter fullscreen mode Exit fullscreen mode

Use new Promise mainly to wrap callback APIs:

function wait(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}
Enter fullscreen mode Exit fullscreen mode

15. Real-World Pitfall

I once expected a boolean, but got a Promise.

function isWindows11OrLater() {
  return navigator.userAgentData
    .getHighEntropyValues(["platformVersion"])
    .then((ua) => {
      if (navigator.userAgentData.platform !== "Windows") {
        return false;
      }

      const version = parseInt(ua.platformVersion.split(".")[0], 10);
      return version >= 13;
    });
}

async function main() {
  const result = await isWindows11OrLater();
  console.log(result);
}
Enter fullscreen mode Exit fullscreen mode

The reason:

the API itself is asynchronous, so it returns a Promise.


Summary

  • Promise → a future value
  • async → always returns a Promise
  • await → unwraps that value

Once this clicks, async JavaScript becomes much easier.

You will use this everywhere:

  • API calls
  • data fetching
  • parallel execution
  • error handling

Understanding Promise first makes async / await much clearer.

Top comments (0)