CodeMasteryLab
Section 8

Asynchronous JavaScript: Callbacks, Promises & Async/Await

The most critical modern JS topic — event loop, callback hell, Promises, async/await, and error handling patterns.

Asynchronous JavaScript

Async programming is tested in every mid-to-senior JavaScript interview. Companies use it to assess whether you understand how JS handles concurrency despite being single-threaded.

The Event Loop

Call Stack → Web APIs → Callback Queue → Event Loop → Call Stack
  1. JS runs code on the Call Stack (single-threaded)
  2. Async operations (setTimeout, fetch) are handed to Web APIs
  3. When complete, callbacks go to the Callback Queue (macrotask) or Microtask Queue (Promises)
  4. Event Loop moves callbacks to Call Stack when it's empty
  5. Microtasks (Promises) always run before macrotasks (setTimeout)
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Output: 1, 4, 3, 2

Callbacks → Promises → Async/Await

// Callback hell
getUser(id, (user) => {
  getPosts(user.id, (posts) => {
    getComments(posts[0].id, (comments) => {
      // deeply nested...
    });
  });
});

// Promise chain
getUser(id)
  .then(user => getPosts(user.id))
  .then(posts => getComments(posts[0].id))
  .then(comments => console.log(comments))
  .catch(err => console.error(err));

// Async/Await (cleanest)
async function loadData(id) {
  try {
    const user = await getUser(id);
    const posts = await getPosts(user.id);
    const comments = await getComments(posts[0].id);
    return comments;
  } catch (err) {
    console.error(err);
  }
}