Search lands in PR-5.1 (Pagefind).

Explanation Intermediate

Chapter 15 Updated

Creating Promises, Chaining, Error Handling & async/await

The producer side of promises and the modern way to write async code with async/await.

  • Full 13m
  • Revision 3m
  • Flow 2m

Episode 22 — Creating a Promise (The Producer Side)

Consumer vs Producer — Two Sides of Every Promise

In Episode 21, we learned how to consume promises with .then. Now we learn how to create (produce) them. In real applications, you’ll do both — creating promises for your own async logic, and consuming promises from APIs like fetch.

Anatomy of Creating a Promise

The full createOrder implementation — line by line
function createOrder(cart) {
  // Step 1: Create a new Promise using the Promise constructor
  // The constructor takes an EXECUTOR function
  // JS gives us two functions: resolve (for success) and reject (for failure)
  const promise = new Promise(function(resolve, reject) {
 
    // Step 2: Write your async logic inside
    // This executor runs IMMEDIATELY (synchronously!)
 
    // Step 3: Validate — if something is wrong, REJECT
    if (!validateCart(cart)) {
      const err = new Error("Cart is not Valid");
      reject(err);
      // The promise state changes: pending → rejected
      // .catch() handlers will be triggered
      return; // ← important! Stop execution after reject
    }
 
    // Step 4: Do the actual work (DB call, API call, etc.)
    const orderId = "12345"; // simulating DB response
 
    // Step 5: If successful, RESOLVE with the result
    if (orderId) {
      resolve(orderId);
      // The promise state changes: pending → fulfilled
      // .then() handlers will be triggered with orderId
    }
  });
 
  // Step 6: RETURN the promise to the consumer
  return promise;
}

What happens when you log the promise immediately?

const cart = ["shoes", "pants", "kurta"];
const promise = createOrder(cart);
 
console.log(promise); // Promise {<pending>}
// WHY pending? Because JS doesn't wait for the promise to resolve.
// It moves to the next line immediately.
// The promise will resolve LATER, and .then will fire at that point.
 
promise.then(function(orderId) {
  console.log(orderId); // "12345" — runs when promise is fulfilled
});

Error Handling with .catch()

Catching rejected promises

When reject(err) is called inside the promise, the promise enters the rejected state. If you don’t handle this with .catch(), the browser throws an Uncaught (in promise) Error. Always handle rejections.

Consumer handles both success and failure
const cart = ["shoes", "pants", "kurta"];
 
createOrder(cart)
  .then(function(orderId) {
    // ✅ This runs if promise is FULFILLED (resolve was called)
    console.log("Order created:", orderId);
  })
  .catch(function(err) {
    // ❌ This runs if promise is REJECTED (reject was called)
    console.log("Error:", err.message);
  });

Promise Chaining — The Complete Pattern

The Rule of Chaining

In promise chaining, whatever you return from one .then becomes the data for the next .then. If you return a value, the next .then gets that value. If you return a promise, the chain waits for it to settle.

✅ Complete chain with return statements
const cart = ["shoes", "pants", "kurta"];
 
createOrder(cart)
  .then(function(orderId) {
    console.log(orderId);     // "12345"
    return orderId;            // ← MUST return for next .then to receive it
  })
  .then(function(orderId) {
    return proceedToPayment(orderId); // ← returns a PROMISE
  })
  .then(function(paymentInfo) {
    console.log(paymentInfo); // "Payment Successful"
  })
  .catch(function(err) {
    console.log(err.message); // catches errors from ANY step above
  });
 
// proceedToPayment also returns a promise:
function proceedToPayment(orderId) {
  return new Promise(function(resolve, reject) {
    resolve("Payment Successful");
  });
}

Advanced: Multiple .catch() Blocks — Selective Error Handling

You can place .catch() at different levels to handle errors selectively. A .catch only handles errors from the steps above it. Steps below it continue to run.

Selective error handling
createOrder(cart)
  .then(function(orderId) {
    console.log(orderId);
    return orderId;
  })
  .catch(function(err) {
    // ← This catches ONLY errors from createOrder and the .then above
    // Steps below this .catch STILL RUN even if an error was caught!
    console.log("Order error:", err.message);
  })
  .then(function(orderId) {
    // ← This RUNS even if createOrder failed!
    // But orderId will be undefined (catch doesn't pass data)
    return proceedToPayment(orderId);
  })
  .then(function(paymentInfo) {
    console.log(paymentInfo);
  })
  .catch(function(err) {
    // ← This catches errors from proceedToPayment and above
    console.log("Payment error:", err.message);
  });

Episode 23 — async/await

The modern, cleaner way to write async code — and what actually happens behind the scenes.

What is async?

async is a keyword that makes a function return a promise

When you put async before a function, two things change:

  • The function always returns a promise — even if you return a plain value, it gets wrapped in a promise automatically.
  • You can now use the await keyword inside it.
async wraps return values in a promise
async function getData() {
  return "Namaste JavaScript";
  // Even though we return a string, async wraps it:
  // Equivalent to: return Promise.resolve("Namaste JavaScript")
}
 
const dataPromise = getData();
console.log(dataPromise);
// Promise {<fulfilled>: 'Namaste JavaScript'}
 
// To get the actual value:
dataPromise.then(res => console.log(res));
// "Namaste JavaScript"

If you return a promise from async function — it doesn’t double-wrap

Returning a promise from async — no double wrapping
const p = new Promise(resolve => resolve("Already a promise!"));
 
async function getData() {
  return p; // returning an existing promise
}
 
const result = getData();
console.log(result);
// Promise {<fulfilled>: 'Already a promise!'}
// NOT Promise {Promise {<fulfilled>:...}} — no double-wrapping!

What is await? — The Real Magic

await pauses the function (NOT the engine)

await can ONLY be used inside an async function. It makes JS wait for the promise to resolve before moving to the next line — but ONLY inside that function. The rest of the program continues running.

The Key Difference: .then vs await

Promise.then way:

Promise.then way
function getData() {
  p.then(res => console.log(res));
  console.log("Hello!");
}
getData();
// "Hello!" prints FIRST
// Then promise result prints LATER

JS registers the .then callback and moves on. “Hello!” prints before the promise resolves.

async/await way:

async/await way
async function getData() {
  const val = await p;
  console.log("Hello!");
  console.log(val);
}
getData();
// WAITS for promise to resolve
// Then "Hello!" and val print together

The function pauses at await until the promise settles. Then continues from where it left off.

Behind the Scenes — What Actually Happens with await

JS is NOT actually waiting — the function is SUSPENDED

Remember the golden rule: “Time, Tide, and JS wait for none.” The call stack is never blocked. Here’s what really happens:

The behind-the-scenes mechanics
const p1 = new Promise(resolve =>
  setTimeout(() => resolve("p1 done"), 5000)
);
const p2 = new Promise(resolve =>
  setTimeout(() => resolve("p2 done"), 10000)
);
 
async function handlePromise() {
  console.log("Hi");         // 1. Prints immediately
 
  const val = await p1;      // 2. Function SUSPENDS here
  console.log("Hello!");     // 4. Prints after 5 seconds
  console.log(val);          // 5. "p1 done"
 
  const val2 = await p2;     // 6. Checks if p2 resolved — waits if not
  console.log("Hello! 2");   // 7. Prints after 10 seconds from start
  console.log(val2);         // 8. "p2 done"
}
 
handlePromise();            // 3. This returns, stack is free!

Call Stack Flow — Step by Step

handlePromise() pushed → logs “Hi” → sees await p1, SUSPENDS → handlePromise() popped off stack → stack is FREE, other code runs → p1 resolves (5s) → handlePromise() pushed BACK → resumes from where it left off → logs “Hello!”, logs val → sees await p2, SUSPENDS again

The Timing Trap — Promises Start Immediately!

await doesn’t START the promise — it waits for an already-running one

Both timers start at the SAME time
const p1 = new Promise(resolve =>
  setTimeout(() => resolve("p1"), 10000)  // 10 second timer starts NOW
);
const p2 = new Promise(resolve =>
  setTimeout(() => resolve("p2"), 5000)   // 5 second timer starts NOW
);
// ⚠️ BOTH timers are already ticking!
 
async function handle() {
  console.log("Hi");             // instant
 
  const val1 = await p1;        // waits for p1 (10s)
  console.log(val1);            // "p1" at 10s
 
  const val2 = await p2;        // p2 already resolved at 5s!
  console.log(val2);            // "p2" prints IMMEDIATELY after p1
}
handle();
// Output: "Hi" → (10s wait) → "p1" → "p2" (instant!)

How to make p2 actually wait AFTER p1

If you want truly sequential execution (p2 starts only after p1 finishes), don’t create the promises upfront — create them inside the async function:

✅ Sequential — p2 starts after p1 finishes
function createP1() {
  return new Promise(resolve =>
    setTimeout(() => resolve("p1 done"), 10000)
  );
}
function createP2() {
  return new Promise(resolve =>
    setTimeout(() => resolve("p2 done"), 5000)
  );
}
 
async function handle() {
  const val1 = await createP1(); // p1 CREATED and waited
  console.log(val1);             // "p1 done" at 10s
 
  const val2 = await createP2(); // p2 CREATED here — starts NOW
  console.log(val2);             // "p2 done" at 15s (10 + 5)
}
handle();

Real-World Example — fetch with async/await

Fetching data from an API

The practical pattern you'll use daily
async function handlePromise() {
  // fetch() returns a Response object (wrapped in a promise)
  const response = await fetch("https://api.github.com/users/alok722");
 
  // response.json() is ALSO a promise — it parses the body
  const data = await response.json();
 
  console.log(data);
}
handlePromise();

Error Handling in async/await

Two ways to handle errors

Way 1: try/catch — the recommended approach
async function handlePromise() {
  try {
    const response = await fetch("https://invalid-url.com");
    const data = await response.json();
    console.log(data);
  } catch (err) {
    console.log("Error:", err);
    // Catches: network errors, JSON parse errors, any throw
  }
}
handlePromise();
Way 2: .catch() on the async function call
async function handlePromise() {
  const response = await fetch("https://invalid-url.com");
  const data = await response.json();
  console.log(data);
}
 
// Since async functions return promises, you can .catch() them:
handlePromise().catch(err => console.log("Error:", err));

async/await vs .then/.catch — Which to Use?

They’re the same thing underneath

async/await is syntactic sugar over promises. Behind the scenes, it uses the exact same Promise mechanism. The difference is purely in how you write and read the code.

  • async/await → reads like synchronous code, easier to debug (step-through in debugger), better for sequential operations, cleaner try/catch error handling
  • .then/.catch → better for simple one-off promises, useful when you need to run multiple promises in parallel (with Promise.all), more explicit about what’s async

Recommendation: Use async/await as the default. Fall back to .then for specific patterns like Promise.all, Promise.race, or when you need to fire-and-forget.

Interview Questions & Answers

Q1. What is an async function? How is it different from a normal function?

An async function is a function declared with the async keyword. It differs from a normal function in two ways: (1) it always returns a promise — if you return a plain value, it’s automatically wrapped in Promise.resolve(value); if you return a promise, it’s returned as-is without double-wrapping. (2) You can use the await keyword inside it to pause execution until a promise resolves. Normal functions cannot use await.

Q2. Does await block the main thread / call stack?

No! await does NOT block the call stack. When JS encounters await, it suspends the async function and pops it off the call stack. The stack is now free — other code, event handlers, and UI updates can run normally. When the awaited promise resolves, the event loop pushes the function back onto the stack, and it resumes from where it left off. This is why the page doesn’t freeze during await. The function appears to “wait,” but the engine is not actually blocked.

Q3. When do promises start executing — when created or when awaited?

Promises start executing immediately when created, not when awaited. The executor function inside new Promise((resolve, reject) => { ... }) runs synchronously at creation time. await doesn’t start the promise — it only waits for an already-running promise to settle. This is why if you create two promises with const p1 = ... and const p2 = ..., both start their timers immediately. Awaiting them sequentially doesn’t make them run sequentially — they’re already running in parallel.

Q4. How do you handle errors in async/await?

Two approaches: (1) try/catch (recommended) — wrap await calls in a try block, handle errors in catch. This keeps error handling co-located with the code, allows specific handling per step, and reads naturally. (2) .catch() on the function call — since async functions return promises, you can do handlePromise().catch(err => ...). This handles errors externally. Both work because async/await is sugar over promises. Use try/catch in production for better readability.

Q5. What is the difference between async/await and Promise.then?

They are functionally identical — async/await is syntactic sugar over promises. The differences are in code style: (1) Readability — async/await reads top-to-bottom like synchronous code; .then chains read inside-out. (2) Error handling — async/await uses try/catch; .then uses .catch(). (3) Debugging — async/await is easier to step through in debuggers. (4) Parallel execution — .then with Promise.all is more natural for parallel tasks. Use async/await as the default; use .then for Promise.all/race patterns.

Q6. What is the common pitfall in promise chaining?

The most common pitfall is forgetting to return from inside .then(). If you don’t return, the next .then receives undefined instead of the actual data or promise. The rule is: whatever you return from one .then becomes the input to the next .then. If you return a promise, the chain waits for it. If you return a value, the next .then gets it immediately. If you return nothing, the next .then gets undefined.

Output-Based Interview Questions

I/O 1. What will be the output and timing?

JavaScript
const p = new Promise(resolve =>
  setTimeout(() => resolve("Done!"), 3000)
);
 
async function handle() {
  console.log("A");
  const val = await p;
  console.log("B");
  console.log(val);
}
 
console.log("C");
handle();
console.log("D");

Output: "C" → "A" → "D" → (3s) → "B" → "Done!"

Why: “C” (sync). handle() called → “A” (sync part). await suspends handle, pops it off stack. “D” (sync). After 3s, p resolves → handle resumes → “B” → “Done!”.

I/O 2. What will be the output? (Two awaits, same promise)

JavaScript
const p = new Promise(resolve =>
  setTimeout(() => resolve("value"), 3000)
);
 
async function handle() {
  console.log("Hi");
 
  const val1 = await p;
  console.log("A", val1);
 
  const val2 = await p;
  console.log("B", val2);
}
handle();

Output: "Hi" → (3s) → "A value" → "B value" (instant)

Why: Both await the SAME promise. After 3s it resolves. First await gets the value, second await gets it instantly (already resolved). They don’t wait 3+3=6 seconds — just 3 total.

I/O 3. What will be the output? (p1=10s, p2=5s, await p1 first)

JavaScript
const p1 = new Promise(r => setTimeout(() => r("p1"), 10000));
const p2 = new Promise(r => setTimeout(() => r("p2"), 5000));
 
async function handle() {
  console.log("Hi");
  const v1 = await p1; console.log(v1);
  const v2 = await p2; console.log(v2);
}
handle();

Output: "Hi" → (10s) → "p1" → "p2" (instant!)

Why: Both promises start their timers at creation. p2’s 5s timer finishes at 5s, p1’s 10s timer finishes at 10s. We await p1 first (waits 10s). By then p2 has been resolved for 5 seconds already. So await p2 returns immediately.

I/O 4. What will be the output? (Chaining without return)

JavaScript
Promise.resolve(10)
  .then(val => {
    console.log(val);     // A
    val * 2;              // ← no return!
  })
  .then(val => {
    console.log(val);     // B
    return val * 2;      // ← has return
  })
  .then(val => {
    console.log(val);     // C
  });

Output: 10 → undefined → NaN

Why: A: 10 (correct). B: undefined (first .then didn’t return). C: undefined * 2 = NaN. The missing return in the first .then breaks the entire chain’s data flow.

Quick Revision — Cheat Sheet

Episode 22 — Creating Promises & Chaining

  • Producer pattern: new Promise((resolve, reject) => { ... }) → do work → call resolve(value) or reject(error) → return the promise
  • Executor function runs synchronously
  • resolve() doesn’t stop execution — always add return after reject
  • Chaining rule: return from .then → data flows to next .then. Forget return → next .then gets undefined
  • .catch() catches errors from all steps above it in the chain
  • .then after .catch still runs — catch returns a resolved promise
  • Multiple .catch blocks for selective error handling at different chain levels
  • Arrow functions with implicit return make chains cleaner

Episode 23 — async/await

  • async function always returns a promise. Plain values auto-wrapped, existing promises returned as-is.
  • await pauses the function only — NOT the call stack. Function is suspended, popped off stack, resumed when promise settles.
  • Promises start executing when created, not when awaited. await only waits for already-running promises.
  • To make promises truly sequential: create them inside the async function with factory functions, not upfront.
  • Error handling: try/catch inside async function (recommended) or .catch() on the function call.
  • fetch() needs two awaits: one for the Response, one for .json() (body parsing).
  • async/await is syntactic sugar over promises — same mechanism underneath.
  • Use async/await as default. Use .then for Promise.all/race/any patterns.

Comments

Comments are disabled in this environment. Set PUBLIC_GISCUS_REPO, PUBLIC_GISCUS_REPO_ID, and PUBLIC_GISCUS_CATEGORY_ID to enable.