W3docs

JavaScript Async Iterators and Generators

Learn JavaScript async iterators and async generators: Symbol.asyncIterator, async function*, for await...of, and lazy pagination patterns.

Asynchronous programming is a cornerstone of modern JavaScript development, allowing developers to write non-blocking, concurrent code that can efficiently handle tasks such as network requests, file I/O, and timers. This guide covers async iterators and async generators, two features introduced in ECMAScript 2018 that let you iterate over data that arrives over time — one chunk per network round-trip, one event per user action — without blocking the rest of your program.

This page assumes you are comfortable with regular iterables, generators, and promises. If any of those are new, read them first.

Understanding Async Iterators

What are Async Iterators?

Async iterators are a special type of iterator designed to handle asynchronous data streams. Unlike traditional iterators, which operate synchronously, async iterators enable developers to iterate over sequences of asynchronous values, such as promises or streams, in a non-blocking manner.

Technically, an object is considered an async iterable if it implements the Symbol.asyncIterator method, which returns an async iterator object. Here is a practical example of manually implementing this interface on a custom object:

javascript— editable

Sync Iterators vs. Async Iterators

The difference between a regular iterable and an async iterable comes down to three things: the method name, the return type of next(), and the loop used to consume it.

Sync iterableAsync iterable
MethodSymbol.iteratorSymbol.asyncIterator
next() returns{ value, done }a Promise of { value, done }
Consuming loopfor...offor await...of
Generator syntaxfunction*async function*

Because next() returns a Promise in the async case, each step of the loop can wait for an asynchronous operation — a fetch, a timer, a database read — before the next value is produced. A plain for...of cannot do that: it expects value/done to be available immediately. Trying to use for await...of on a sync-only iterable still works (the engine wraps the values in resolved promises), but using a sync for...of on an async iterable does not — you would just iterate over pending Promise objects.

How to Use Async Iterators

To leverage async iterators in your JavaScript code, you first need to understand their fundamental concepts and syntax. Let's explore a simple example to demonstrate how async iterators work in practice:

javascript— editable

In this example, we define an async generator function generateNumbers() that yields a sequence of numbers asynchronously. We then create an async iterable from the generator function and use a for await...of loop to iterate over the values produced by the async iterator.

Note: When you yield a plain value inside an async function*, the iterator's next() method automatically returns a Promise that resolves to { value: <your value>, done: false }. You only need to explicitly yield a Promise if you want the consumer to receive a Promise object rather than the resolved value.

Real-World Applications of Async Iterators

Async iterators find widespread use in scenarios involving asynchronous data processing, such as fetching data from external APIs, reading from streams, or handling asynchronous events. Their versatility and efficiency make them indispensable tools for modern JavaScript developers seeking to write scalable and responsive applications.

Exploring JavaScript Generators

Introduction to Generators

Generators are a powerful feature introduced in ECMAScript 2015 that enable the creation of iterable sequences with custom iteration logic. Unlike traditional functions, which execute to completion upon invocation, generators can pause and resume their execution, allowing for lazy evaluation of values.

It is important to distinguish between standard generators and async generators:

  • Standard Generators (function*): Yield values synchronously.
  • Async Generators (async function*): Return a Promise from each next() call, allowing the consumer to wait for each value using for await...of.

Leveraging Generators for Asynchronous Programming

One of the most compelling use cases for generators is asynchronous programming. By combining generators with promises, developers can create asynchronous workflows that are both elegant and easy to reason about. Here is a modern example of using an async generator to fetch and yield data from a remote server:

javascript— editable

In this example, we define an async generator function fetchTodos() that asynchronously fetches data from a remote API using the fetch() function. By using await inside the generator and yielding individual items, we can stream the results directly into a for await...of loop without manual .next() calls or promise chaining.

Paginated Fetch with an Async Generator

The pattern that makes async generators shine is lazy pagination. Many APIs return results in pages and expect you to keep requesting the next page until there are no more. An async generator can hide all of that bookkeeping: it fetches a page, yields its items one by one, and only requests the next page when the consumer asks for more. The caller can stop early — for example after finding what it needs — and no further network requests are made.

javascript— editable

Notice the break: because the generator is lazy, exiting the loop after 25 items means the generator never requests page 3. This is what separates an async generator from fetching everything up front into an array — you pay only for the data you actually use.

Advanced Generator Patterns

Generators offer a plethora of advanced patterns and techniques for solving complex programming problems. Here are a few examples showcasing their versatility:

  • Parallel Execution: By initiating multiple generators and managing their promises concurrently, you can perform several asynchronous tasks at once.
  • Error Handling: Employ try-catch blocks within generators to gracefully handle rejected promises yielded during the iteration process.
  • Data Pipelines: Build data processing pipelines by chaining generators together, where the output of one generator serves as the input for the next.
javascript— editable

Conclusion

In conclusion, async iterators and generators are indispensable tools in the modern JavaScript developer's arsenal. By mastering these powerful features, you can unlock new dimensions of expressiveness and efficiency in your asynchronous code. Whether you're building web applications, server-side APIs, or command-line utilities, async iterators and generators empower you to tackle complex asynchronous challenges with ease. Start incorporating async iterators and generators into your JavaScript projects today and elevate your programming skills to new heights!

  • Iterables — the synchronous foundation behind for...of and Symbol.iterator.
  • Generators — the function* syntax that async generators build on.
  • Promises — what each await inside an async generator resolves.
  • Async/await — the syntax for await...of is paired with.

Practice

Practice
What is true about JavaScript's async Iterators and generators?
What is true about JavaScript's async Iterators and generators?
Was this page helpful?