async await in JavaScript free code

If you understand this article fully, you will never be confused by async await in JavaScript code again.

We’ll cover:

  1. What async/await REALLY is
  2. How JavaScript executes it internally
  3. Event loop interaction (critical)
  4. Error handling (real rules)
  5. Performance traps
  6. Real-world patterns
async await in JavaScript

1️⃣ First: What async/await is NOT

❌ It is not multithreading
❌ It does not block JavaScript
❌ It does not pause the whole program

✅ It is syntax sugar on top of Promises

2️⃣ The Single Most Important Rule

Every async function ALWAYS returns a Promise

No exception. Ever.

Example 1: Returning a value

async function getNumber() {
  return 42;
}

Looks like it returns 42, right?

❌ Wrong.

getNumber(); // Promise { 42 }

JavaScript rewrites this internally as:

function getNumber() {
  return Promise.resolve(42);
}

Example 2: Throwing an error

async function fail() {
  throw new Error("Oops");
}

This becomes:

function fail() {
  return Promise.reject(new Error("Oops"));
}

🔥 This is CRITICAL for understanding error handling.

3️⃣ What EXACTLY does await do?

Key rule

await pauses ONLY the current async function, not JavaScript.

Example

console.log("A");

async function test() {
  console.log("B");
  await Promise.resolve();
  console.log("C");
}
test();
console.log("D");

Output

A
B
D
C
What EXACTLY does await do

Step-by-step execution

  1. console.log("A") → prints A
    • Added to the call stack
    • Executes immediately
    • Prints A
    • Removed from the stack
  2. test() starts
    • Function is defined
    • Nothing executes yet
  3. console.log("B") → prints B
    • test() is pushed onto the call stack
    • JavaScript starts executing its body
  4. await Promise.resolve()
    → function is paused
    • Executes synchronously
    • Prints B
  5. Control returns to the main thread
    • Promise.resolve() creates an already-resolved promise
    • .then() The callback is scheduled as a microtask
    • test() function is SUSPENDED
    • Execution exits test()
    • The call stack is cleared
  6. console.log("D") → prints D
    • Added to the call stack
    • Executes immediately
    • Prints D
    • Removed from the stack
  7. Promise resolves → function resumes
  8. console.log("C") → prints C
    • This resumes the test() function right after await.
    • console.log("C") runs
    • Prints C
    • Async function finishes

4️⃣ How await Works with the Event Loop

When JavaScript sees:

await somePromise;

Internally, it does:

  1. Convert value to a promise Promise.resolve(somePromise)
  2. Suspend function execution
  3. Schedule continuation in the microtask queue
  4. Resume after the current call stack finishes

Why this matters

Microtasks run:

  • AFTER the current code
  • BEFORE setTimeout

Proof

setTimeout(() => console.log("timeout"), 0);

Promise.resolve().then(() => console.log("promise"));

async function test() {
  await Promise.resolve();
  console.log("await");
}

test();

Output

promise
await
timeout

🔥 This explains MANY “why did this run first?” questions.

5️⃣ await with NON-Promises

await 10;

This works because JavaScript converts it to:

await Promise.resolve(10);

So these are equivalent:

await fetch(url);
await Promise.resolve(fetch(url));

❌ Why this does NOT work

await setTimeout(() => {}, 1000);

Because:

  • setTimeout returns undefined
  • await undefined resolves immediately

✅ Correct sleep implementation

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

await sleep(1000);

6️⃣ Error Handling — REAL RULES

Rule 1

try/catch only catches errors inside the SAME async function

❌ Wrong assumption

async function a() {
  b();
}

async function b() {
  throw new Error("fail");
}

This error is NOT caught in a.

✅ Correct

async function a() {
  try {
    await b();
  } catch (e) {
    console.error(e);
  }
}

Rule 2

await turns promise rejection into a thrown error

await Promise.reject("error");

Is the same as:

throw "error";

7️⃣ async/await vs .then() (Internals)

These two are IDENTICAL:

await fetch(url);
fetch(url).then(res => res);

So async/await:

  • does NOT make code faster
  • does NOT change execution model
  • ONLY improves readability

8️⃣ Performance: Sequential vs Parallel

❌ Sequential (Slow)

const user = await getUser();   // 1s
const posts = await getPosts(); // 1s

⏱ Total = 2 seconds

✅ Parallel (Fast)

const [user, posts] = await Promise.all([
  getUser(),
  getPosts()
]);

⏱ Total = 1 second

Golden Rule

Only await sequentially when results DEPEND on each other.

9️⃣ Common async/await Bugs

❌ Bug 1: Forgetting await

const data = fetch(url);
console.log(data.user); // ❌ Promise

❌ Bug 2: async inside forEach

array.forEach(async item => {
  await save(item);
});

forEach does NOT wait.

✅ Fix

for (const item of array) {
  await save(item);
}

OR

await Promise.all(array.map(save));

🔟 async/await in Frameworks

React

useEffect(() => {
  (async () => {
    const data = await fetchData();
    setData(data);
  })();
}, []);

Why?

  • useEffect callback cannot be async

Node.js / Express

app.get("/", async (req, res) => {
  try {
    const user = await db.findUser();
    res.json(user);
  } catch {
    res.status(500).send("Error");
  }
});

🧠 Mental Model

Think of await as:

“Pause THIS function, let JS continue everything else, then come back here later.”

JavaScript:

  • keeps running
  • keeps rendering
  • keeps handling events

🏆 Production Best Practices: async await in JavaScript

✔ Always handle errors
✔ Use Promise.all for parallel work
✔ Never assume async data exists
✔ Avoid async in forEach
✔ Combine with optional chaining