DEV Community

Cover image for JavaScript Event Loop & Promises Explained: Async Programming Made Easy
Cameron Lucas
Cameron Lucas

Posted on

JavaScript Event Loop & Promises Explained: Async Programming Made Easy

JavaScript's Async Superpower: The Event Loop & Promises

Ever ordered food at a restaurant and wondered how the kitchen juggles multiple orders at once? The chef (JavaScript) prepares one dish at a time, but they don't just sit idle while waiting for the oven to finish baking your pizza. Instead, they move on to prepare the next dish. This is exactly how JavaScript's event loop worksโ€”handling tasks efficiently while waiting on others. ๐Ÿš€

JavaScript is an idiot sandwhich

But what happens when you need something done ASAP, like getting a drink while waiting for food? That's where Promises come in, ensuring tasks complete when ready without blocking everything else.

If you've ever run into mysterious async behavior, buckle up! We're diving into the event loop and Promises to demystify how JavaScript actually handles asynchronous tasks.


What is the JavaScript Event Loop?

JavaScript is single-threaded, meaning it can only do one thing at a time. However, thanks to the event loop, it can appear to multitask by juggling asynchronous operations like fetching data, handling user input, and running timers.

Ricky Bobby First or Last

๐Ÿšฆ How the Event Loop Works

  1. Call Stack (Synchronous Tasks) โ€“ JavaScript executes code line by line in a stack.
  2. Web APIs (Async Tasks) โ€“ Certain operations like setTimeout, fetch(), and event listeners are handled outside the stack.
  3. Callback Queue (Macrotasks) โ€“ Once an async task completes, its callback moves here.
  4. Microtask Queue (Higher Priority) โ€“ Includes Promise callbacks, executed before the macrotask queue.
  5. Event Loop Checks & Executes โ€“ The event loop constantly checks if the call stack is empty and processes queued tasks.

๐Ÿ›  Code Example: The Event Loop in Action

console.log('Start');

setTimeout(() => {
    console.log('Timeout Callback');
}, 0);

Promise.resolve().then(() => {
    console.log('Promise Callback');
});

console.log('End');
Enter fullscreen mode Exit fullscreen mode

๐Ÿ” Expected Output:

Start
End
Promise Callback
Timeout Callback
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”‘ Why?

  1. console.log('Start') runs immediately.
  2. setTimeout sends its callback to the Web API, scheduling it for later.
  3. Promise.resolve().then(...) goes into the Microtask Queue.
  4. console.log('End') runs before any async tasks.
  5. The Microtask Queue (Promises) executes before the Macrotask Queue (setTimeout), so the Promise logs first.

JavaScript Promises: How They Work & Why They Matter

Before Promises, we had callbacks, which led to deeply nested, unreadable code known as "callback hell". Promises make async operations cleaner and more manageable.

Callback Hell

๐Ÿ— Creating a Promise

const fetchData = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Data received!');
        }, 2000);
    });
};

fetchData().then(response => console.log(response));
Enter fullscreen mode Exit fullscreen mode

What happens?

  • fetchData() returns a Promise that resolves after 2 seconds.
  • .then() waits for it to complete before logging the response.

๐Ÿ” Chaining Promises

Multiple async operations? No problem!

fetchData()
    .then(data => {
        console.log(data);
        return 'Next step';
    })
    .then(step => console.log(step));
Enter fullscreen mode Exit fullscreen mode

Each .then() returns a new Promise, allowing sequential execution.


Understanding JavaScript Promise Methods

When dealing with multiple asynchronous operations, JavaScript provides built-in Promise methods to simplify handling multiple Promises. These methods help determine how and when Promises resolve or reject, making async code more predictable and efficient.

Cool Spongebob

๐Ÿ“Œ Why Use Promise Methods?

  • Better control over multiple async operations โ€“ Execute them concurrently or sequentially.
  • Optimize performance โ€“ Avoid unnecessary delays or failures.
  • Improve error handling โ€“ Gracefully manage failures across multiple Promises.

๐Ÿ”— Promise.all() โ€“ Running Multiple Promises in Parallel

This method takes an array of Promises and resolves when all of them complete.

const p1 = Promise.resolve('One');
const p2 = new Promise(res => setTimeout(() => res('Two'), 2000));
const p3 = new Promise(res => setTimeout(() => res('Three'), 1000));

Promise.all([p1, p2, p3]).then(values => console.log(values));
// ['One', 'Two', 'Three'] (After 2 seconds)
Enter fullscreen mode Exit fullscreen mode

๐ŸŽ Promise.race() โ€“ First One Wins

Returns the first resolved or rejected Promise.

Promise.race([p1, p2, p3]).then(value => console.log(value));
// 'One' (since p1 resolves immediately)
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”„ Promise.allSettled() โ€“ Wait for Everything, Regardless of Success

Resolves when all Promises settle (resolve or reject), returning the results.

Promise.allSettled([p1, p2, Promise.reject('Error')])
  .then(results => console.log(results));
Enter fullscreen mode Exit fullscreen mode

๐Ÿšจ Promise.any() โ€“ First Successful Promise

Similar to race(), but ignores rejections and resolves to the first fulfilled Promise.

Promise.any([
  Promise.reject('Fail 1'),
  Promise.reject('Fail 2'),
  Promise.resolve('Success')
]).then(value => console.log(value));
// 'Success'
Enter fullscreen mode Exit fullscreen mode

Summary: Key Takeaways

โœ… The Event Loop ensures JavaScript handles async tasks efficiently.
โœ… Microtask Queue (Promises) always executes before the Macrotask Queue (setTimeout, etc.).
โœ… Promises provide a cleaner way to handle async operations, replacing callback hell.
โœ… Promise methods like all, race, allSettled, and any help manage multiple async tasks.
โœ… Understanding the event loop helps debug async behavior and optimize performance.

๐Ÿš€ Up Next: Async/Await โ€“ The Next Level of Promises!

Top comments (0)