DEV Community

HARSHITH GADDAM
HARSHITH GADDAM

Posted on

My Javascript Learning Journey:Async,Await,iterators & Generators

Today I learned several important JavaScript concepts related to asynchronous programming and iteration. Instead of only learning how these features work, I focused on understanding why they were introduced, the problems they solve, and where they are used in real-world applications.

Async/Await – Syntax Sugar over Promises

One of the biggest things I learned today is that async/await does not replace Promises. It is simply a cleaner and more readable way to work with them.

When we write:

async function getUser() {
    const user = await fetchUser();
    return user;
}
Enter fullscreen mode Exit fullscreen mode

JavaScript is still using Promises internally. The await keyword pauses the execution of the async function until the Promise settles, making asynchronous code look very similar to synchronous code.

Without async/await, the same logic would require multiple .then() calls, making complex asynchronous code harder to read and maintain.


Handling Async Errors with try...catch

When using Promises directly, errors are usually handled with .catch().

fetchUser()
    .then(user => console.log(user))
    .catch(err => console.error(err));
Enter fullscreen mode Exit fullscreen mode

With async/await, the same error handling becomes much cleaner using try...catch.

async function loadUser() {
    try {
        const user = await fetchUser();
        console.log(user);
    } catch (err) {
        console.error(err);
    }
}
Enter fullscreen mode Exit fullscreen mode

I learned that try...catch only works with await inside an async function. If the awaited Promise rejects, JavaScript throws that rejection as an exception, which is then caught by the catch block.

This makes asynchronous error handling look just like normal synchronous error handling.


Promise.all()

Promise.all() is used when multiple asynchronous tasks need to finish before continuing.

For example, imagine a dashboard that needs to load:

  • User Profile
  • Orders
  • Shopping Cart
  • Recommendations

All these requests can start at the same time, but the dashboard should only be shown after every request is completed.

await Promise.all([
    fetchProfile(),
    fetchOrders(),
    fetchCart(),
    fetchRecommendations()
]);
Enter fullscreen mode Exit fullscreen mode

I also learned that Promise.all() does not start the promises. The promises begin executing when they are created (or when the function calls are evaluated). Promise.all() simply waits for all of them to settle successfully.


Can We Display Data One by One?

Yes!

Instead of waiting for every request, each section of a webpage can be displayed as soon as its data arrives.

For example:

  • Show the profile first.
  • Then show the orders.
  • Then display the cart.
  • Finally load the recommendations.

Many websites like YouTube and Amazon use this approach to improve the user experience.


Promise.allSettled()

Sometimes we don't want the whole operation to fail just because one task fails.

For example, while uploading five files:

  • File 1 ✔️
  • File 2 ❌
  • File 3 ✔️
  • File 4 ✔️
  • File 5 ❌

Using Promise.all(), the first failure rejects the entire operation.

Using Promise.allSettled(), JavaScript waits for every promise to finish and tells us which ones succeeded and which ones failed.

This is useful for:

  • File uploads
  • Data synchronization
  • Background jobs
  • Health checks

Promise.race()

Promise.race() returns the result of the first promise that settles.

A common example is implementing request timeouts.

Promise.race([
    fetch("/user"),
    timeoutPromise
]);
Enter fullscreen mode Exit fullscreen mode

If the server responds first, we use the response.

If the timeout finishes first, we stop waiting.


Promise.any()

Promise.any() returns the first successful promise.

Rejected promises are ignored unless every promise fails.

This is useful when working with:

  • Multiple mirror servers
  • Backup APIs
  • CDN replicas

The application simply uses the first successful response.


Promise Combinators Summary

Method Purpose
Promise.all() Wait for every promise to fulfill
Promise.allSettled() Wait for every promise to settle, whether fulfilled or rejected
Promise.race() Use the first promise that settles
Promise.any() Use the first successful promise

Iterators

I learned that JavaScript needed a common way to loop through different types of collections like arrays, strings, maps, and sets.

An iterator is simply an object with a next() method.

Each call to next() returns:

{
    value,
    done
}
Enter fullscreen mode Exit fullscreen mode

for...of internally uses iterators to retrieve values one by one.


Generators

Writing iterators manually requires managing state and keeping track of the current position.

Generators make this much easier.

A generator is declared using:

function* numbers() {
    yield 1;
    yield 2;
    yield 3;
}
Enter fullscreen mode Exit fullscreen mode

The yield keyword pauses execution and remembers the current state.

Each call to next() resumes execution from where it previously stopped.

Generators automatically create iterators, making iteration much simpler.


Async Iterators

Normal iterators work only with values that are immediately available.

But what if the next value comes from:

  • A server
  • A database
  • A file
  • A WebSocket

In these situations, JavaScript introduced async iterators.

Unlike normal iterators, next() returns a Promise that eventually resolves to:

{
    value,
    done
}
Enter fullscreen mode Exit fullscreen mode

This allows JavaScript to wait until the next value becomes available.


Async Generators

Just as generators simplify iterators, async generators simplify async iterators.

They are declared like this:

async function* stream() {
    yield "First";
    yield "Second";
    yield "Third";
}
Enter fullscreen mode Exit fullscreen mode

They can be consumed using:

for await (const item of stream()) {
    console.log(item);
}
Enter fullscreen mode Exit fullscreen mode

This makes working with asynchronous streams much easier.


Where Async Iterators Are Used

Some real-world examples include:

  • Streaming large files
  • Reading database records in batches
  • Live chat messages
  • WebSocket communication
  • AI response streaming (like ChatGPT)
  • Video or audio streaming

Instead of loading everything into memory, data is processed one piece at a time.


My Biggest Takeaways

  • async/await is syntax sugar over Promises and makes asynchronous code easier to read.
  • try...catch provides clean error handling for asynchronous code written with async/await.
  • Promise.all() waits for every promise and fails if any one fails.
  • Promise.allSettled() gives the result of every promise, whether it succeeds or fails.
  • Promise.race() returns whichever promise settles first.
  • Promise.any() returns the first successful promise.
  • Promises begin running when they are created; Promise.all() only waits for them.
  • A webpage doesn't always need to wait for every API before showing content—independent sections can be rendered progressively.
  • Iterators provide a standard way to access values one by one.
  • Generators automatically create iterators and use yield to pause and resume execution.
  • Async iterators return promises from next(), allowing values to arrive over time.
  • Async generators combine async and yield, making asynchronous data streams much easier to work with.

Top comments (0)