I spent way longer than I’d like to admit thinking Promises and async/await were two completely different ways of handling asynchronous code in JavaScript. Like I had to pick a side. Turns out that’s not true at all, and once that clicked for me, a lot of confusing tutorials I’d read finally made sense.
So let me save you the confusion. Async/await isn’t a replacement for Promises. It’s built directly on top of them. It’s syntax sugar. Under the hood, every async function still returns a Promise, and every await is just pausing execution until a Promise resolves. Once you get that, the rest is just about picking the style that fits your situation.
What Promises Actually Do
A Promise is basically a placeholder for a value you don’t have yet. Maybe you’re fetching data from an API, maybe you’re reading a file, whatever it is, the Promise represents “this will eventually finish, either successfully or with an error.”
Here’s the classic pattern:
function getUser(id) {
return fetch(`/api/users/${id}`)
.then(response => response.json())
.then(data => {
console.log(data);
return data;
})
.catch(error => {
console.error('Something broke:', error);
});
}
This works fine. I used this pattern for a solid year before switching most of my code to async/await. The .then() chain is readable when you’ve only got two or three steps. The problem shows up when you start nesting more logic, or when one .then() depends on the result of another asynchronous call. That’s when things start looking messy, and honestly, that messiness is the whole reason async/await exists.
What Async/Await Actually Does
Async/await lets you write asynchronous code that reads top to bottom, like synchronous code. No chaining, no callback-shaped indentation creeping across your screen.
Same example, rewritten:
async function getUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error(‘Something broke:’, error);
}
}
Functionally, this does the exact same thing as the version above. Same Promise underneath, same error handling behavior. The difference is purely how it reads. When I show this to junior devs on my team, this version clicks faster almost every time, especially if they’re coming from a language that doesn’t have Promises as a first-class concept.
Where People Actually Get Tripped Up
I made this mistake myself, so I’ll just say it directly: await only pauses execution inside an async function. You can’t slap await in the middle of regular code and expect it to work. That trips up a lot of people early on.
Another one — running things sequentially when you don’t need to. If you’ve got three independent API calls and you await them one after another, you’re wasting time waiting for each one to finish before starting the next.
// Slower than it needs to be
const user = await getUser(id);
const posts = await getPosts(id);
const comments = await getComments(id);
If none of these depend on each other, run them together instead:
const [user, posts, comments] = await Promise.all([
getUser(id),
getPosts(id),
getComments(id)
]);
This one bit me on a project where a dashboard page was taking almost three seconds to load just because I’d written every fetch sequentially out of habit. Switching to Promise.all cut it down to about the time of the slowest single request instead of the sum of all three.
So Which One Should You Use?
Honestly, for anything beyond a single .then(), I default to async/await now. It’s easier to debug, easier to read back six months later, and error handling with try/catch feels more natural than chaining .catch() at the end of a promise chain.
That said, Promises aren’t going anywhere, and you’ll still reach for methods like Promise.all, Promise.race, or Promise.allSettled even inside async functions. Async/await is a way of writing Promise-based code more comfortably. It’s not a separate system you’re choosing instead of Promises.
If you’re just starting out, learn Promises first so you actually understand what’s happening underneath. Then let async/await be the thing you write day to day.
