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");
Output:
A
B
C
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");
Output:
Start
End
After 3 seconds
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));
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);
Output:
Promise { <pending> }
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));
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";
}
This is the same as:
Promise.resolve("hello")
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();
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);
}
}
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);
}
}
10. Parallel Processing
Sequential:
const a = await fetchA();
const b = await fetchB();
Parallel:
const [a, b] = await Promise.all([
fetchA(),
fetchB()
]);
11. Promise.allSettled
If you need all results:
const results = await Promise.allSettled([
task1(),
task2()
]);
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;
}
Two steps:
- Wait for response
- Wait for JSON parsing
13. Common Mistakes
async returns a Promise
async function test() {
return 123;
}
const x = test(); // Promise
await outside async
function main() {
const x = await test(); // Error
}
Missing error handling
async function main() {
const data = await getData(); // may crash
}
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);
});
}
Good:
function good() {
return fetch("/api/data")
.then((res) => res.json());
}
Use new Promise mainly to wrap callback APIs:
function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
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);
}
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)