Promise .then() vs Async/Await
Same underlying mechanism, two different syntaxes — when each one reads better.
2 min read
.then() chains and async/await aren't competing features — async/await compiles down to the same promise mechanism under the hood. The choice between them is almost entirely about readability, with a couple of situations where one has a genuine, mechanical edge.
Sequential steps: async/await usually wins
When one async step depends on the result of the previous one, async/await reads closer to plain, ordinary code:
// .then() chain
function checkout(cartId) {
return getCart(cartId)
.then(cart => calculateTotal(cart))
.then(total => chargeCard(total))
.then(receipt => emailReceipt(receipt));
}
// async/await
async function checkout(cartId) {
const cart = await getCart(cartId);
const total = await calculateTotal(cart);
const receipt = await chargeCard(total);
return emailReceipt(receipt);
}Both do the same thing. The await version avoids the visual nesting of chained .then() calls, and lets you use ordinary try/catch, if, and loops around each step instead of chaining more .then()/.catch() calls.
Running things in parallel: watch out for accidental sequencing
await, used carelessly, can accidentally serialize work that should run in parallel:
// Slower -- second fetch doesn't start until the first finishes
const userA = await fetchUser(1);
const userB = await fetchUser(2);// Faster -- both requests start immediately, run concurrently
const [userA, userB] = await Promise.all([fetchUser(1), fetchUser(2)]);A .then() chain doesn't have this trap in quite the same way, since starting a promise and calling .then() on it are visually separate steps — but the fix with await (Promise.all) is simple enough that this is more a thing to remember than a real argument for .then().
Handling many independent operations: .then() can be cleaner
For a batch of unrelated promises where you want individual, per-item error handling rather than one try/catch around everything, .then()/.catch() chained directly onto each promise can be more direct:
urls.forEach(url => {
fetch(url)
.then(res => res.json())
.catch(err => console.error(`Failed for ${url}:`, err));
});In practice
Modern JavaScript code overwhelmingly favors async/await for anything with more than one sequential step, because it reads like synchronous code and uses the same try/catch you already know. .then() still shows up for short one-off chains, inside non-async functions, or for attaching a quick handler to a promise you already have. Knowing both matters — nearly every real codebase mixes them, and async/await doesn't remove the need to understand how the promises underneath actually behave.
The next lesson puts promises to practical use: fetching real data from a server with fetch().