When is Promise preferred over async/await in JavaScript?

HighIntermediateJavascript
Quick Answer

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.

Answer

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

Promise.all/allSettled/any/race

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.

Use async/await for readability, Promise APIs for composition-heavy flows.

Example 1: Combinators are naturally Promise-first

When tasks are independent, Promise combinators usually produce cleaner code than manual sequential awaits.

JAVASCRIPT
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.

JAVASCRIPT
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.

JAVASCRIPT
// ✅ 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/await for 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 return in .then()

Broken value propagation

Return each Promise/value explicitly.

Using sequential await for independent calls

Unnecessary latency

Start tasks together and join with Promise.all.

Most bugs come from inconsistent flow shape, not from Promise itself.

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 await inside loops without checking if calls are independent.
      • Over-nesting .then() instead of returning chains cleanly.
      • Ignoring partial-failure strategy (all vs allSettled).
Trade-off or test tip
Test both fast-success and partial-failure cases. Add latency tests to confirm the refactor actually improves total response time.

Still so complicated?

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.

Similar questions
Guides
Preparing for interviews?

Use the relevant interview-question hub first, then move into a concrete study plan before targeted company sets.