Back to JavaScript
JavaScript
easy
mid

How would you write a program that uses both a Promise and an async/await function?

A Promise wraps an async operation: `new Promise((resolve, reject) => …)`. `async/await` is syntactic sugar — `async` makes a function return a Promise; `await` pauses until the awaited Promise settles. Same semantics, cleaner control flow.

3 min read·~8 min to think through

Creating a Promise

js
const wait = (ms) => new Promise((resolve, reject) => {
  if (ms < 0) reject(new Error("ms must be non-negative"));
  else setTimeout(() => resolve(ms), ms);
});

wait(500)
  .then((ms) => console.log("waited", ms))
  .catch((err) => console.error(err));

The executor function receives resolve (fulfill with value) and reject (fail with reason). The Promise can only settle once.

async / await equivalent

js
async function delay(ms) {
  if (ms < 0) throw new Error("ms must be non-negative");
  return new Promise((r) => setTimeout(r, ms));
}

async function main() {
  try {
    await delay(500);
    console.log("done");
  } catch (err) {
    console.error(err);
  }
}

main();

async makes the function return a Promise. await unwraps a Promise to its value (or throws on rejection).

Equivalence

js
async function f() { return 1; }
// equivalent to
function f() { return Promise.resolve(1); }
js
async function f() { await x; doThing(); }
// equivalent to
function f() { return Promise.resolve(x).then(() => doThing()); }

Three states

A Promise is pendingfulfilled or rejected. Once settled, immutable.

Chaining

js
fetchUser()
  .then((user) => fetchPosts(user.id))
  .then((posts) => render(posts))
  .catch((err) => showError(err));

Each .then returns a new Promise; returning a Promise inside flattens (no nesting).

async version

js
async function load() {
  try {
    const user = await fetchUser();
    const posts = await fetchPosts(user.id);
    render(posts);
  } catch (err) {
    showError(err);
  }
}

Reads sequentially; easier to debug.

Parallel

js
const [a, b] = await Promise.all([fetchA(), fetchB()]);   // both at once

Don't do await fetchA(); await fetchB(); if they're independent — that's sequential and wastes time.

Don't forget

  • An async function always returns a Promise.
  • await only inside async (or top-level in modules).
  • Errors in async functions become rejected Promises.
  • Promise.reject(value) and throw inside async are equivalent.
  • An unhandled rejection logs a warning and may crash Node.

Interview framing

"new Promise((resolve, reject) => ...) constructs a promise — call resolve or reject from inside. async makes a function return a Promise; await pauses until the awaited promise settles. They're the same primitive — async/await reads as synchronous code, easier to debug. Independent awaits should be Promise.all'd, not sequential. Errors in async functions become rejected promises — wrap with try/catch or .catch on the call site."

Follow-up questions

  • What's the difference between Promise.all and Promise.allSettled?
  • How do unhandled rejections behave?
  • When would you use Promise constructor directly?

Common mistakes

  • Sequential awaits for independent work.
  • Forgetting to return inside .then.
  • Promise constructor anti-pattern: wrapping an existing Promise.
  • Unhandled rejections.

Performance considerations

  • Microtask queue runs Promise callbacks before next macrotask — fast but can starve I/O if you spin.

Edge cases

  • Resolving with another Promise — it chains.
  • Errors thrown synchronously inside executor still reject.
  • Top-level await only in ES modules.

Real-world examples

  • Every fetch call, db.query, Node's fs.promises, browser APIs.

Senior engineer discussion

Seniors recognize parallel vs sequential opportunity and prefer async/await for readability while using Promise.all/allSettled/race deliberately.

Related questions