async/await improves readability, but Promise APIs are often better for composition, concurrency combinators, and sharing in-flight work. Learn practical cases where direct Promise usage is clearer and more scalable.
When is Promise preferred over async/await in JavaScript?
The core idea
async/await is usually best for readable step-by-step logic. But Promises are often better when you need composition, fan-out/fan-in concurrency, or shareable in-flight results.
Strong interview answer: it is not Promise vs async/await as enemies; async/await is built on Promises. You choose the style that best expresses the control flow.
Situation | Prefer | Why Promise style is stronger |
|---|---|---|
Coordinate many async tasks |
| Combinators express concurrency and outcome policy directly. |
Share one in-flight request across callers | Store and reuse the same Promise | Avoid duplicate network calls and racey cache logic. |
Library/middleware expects a Promise return | Return Promise chain directly | No extra wrapper code; keeps API surface simple. |
Need lazy composition before consumption | Pass Promise through layers and await later | Keeps orchestration flexible at boundaries. |
Example 1: Combinators are naturally Promise-first
When tasks are independent, Promise combinators usually produce cleaner code than manual sequential awaits.
function loadDashboard(userId) {
return Promise.all([
fetch(`/api/users/${userId}`).then((r) => r.json()),
fetch(`/api/orders/${userId}`).then((r) => r.json()),
fetch(`/api/recommendations/${userId}`).then((r) => r.json())
]).then(([user, orders, recs]) => ({ user, orders, recs }));
}
loadDashboard('u42')
.then(renderDashboard)
.catch(showDashboardError);
Example 2: In-flight dedupe cache (Promise is ideal)
A Promise can represent work already started. Multiple callers can await the same Promise safely.
const inFlight = new Map();
function getProduct(productId) {
if (inFlight.has(productId)) return inFlight.get(productId);
const p = fetch(`/api/products/${productId}`)
.then((r) => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
})
.finally(() => inFlight.delete(productId));
inFlight.set(productId, p);
return p;
}
// Both calls reuse one request if concurrent
Promise.all([getProduct('p1'), getProduct('p1')]).then(console.log);
Example 3: avoid unnecessary async wrappers
If a function simply forwards another Promise chain, returning the Promise directly can be cleaner.
// ✅ Promise-forwarding wrapper
function fetchSettings(userId) {
return fetch(`/api/settings/${userId}`).then((r) => r.json());
}
// Also valid with async/await, but no extra value here
async function fetchSettings2(userId) {
const res = await fetch(`/api/settings/${userId}`);
return res.json();
}
Decision rule you can say in interviews
- Use
async/awaitfor imperative, step-by-step business logic. - Use Promise combinators/chains when expressing relationship between multiple async operations is the main problem.
This answer shows design judgment, not just syntax preference.
Common pitfall | Impact | Fix |
|---|---|---|
Mixing styles randomly in one flow | Harder debugging and reviews | Choose one dominant style per function. |
Missing | Broken value propagation | Return each Promise/value explicitly. |
Using sequential await for independent calls | Unnecessary latency | Start tasks together and join with |
Interview one-liner
Promise style is preferred when composing multiple concurrent operations, coordinating outcomes with combinators, or sharing in-flight asynchronous work across callers.
Practical scenario
A product page needs recommendations, inventory, and pricing from separate APIs. Running them sequentially with await adds unnecessary latency, while Promise combinators make concurrency explicit and easier to maintain.
Common pitfalls
- Using
awaitinside loops without checking if calls are independent. - Over-nesting
.then()instead of returning chains cleanly. - Ignoring partial-failure strategy (all vs allSettled).
Test both fast-success and partial-failure cases. Add latency tests to confirm the refactor actually improves total response time.
Use async/await when reading the logic like a story matters most. Use raw Promise APIs when coordinating many moving async pieces is the real challenge.
Use the relevant interview-question hub first, then move into a concrete study plan before targeted company sets.