How Symbol.asyncIterator Works in JavaScript

Symbol.asyncIterator is a built-in JavaScript symbol that defines the asynchronous iteration protocol for an object. By implementing a method with this symbol, an object can yield values asynchronously over time, allowing it to be consumed by the for await...of loop. This mechanism allows developers to iterate over data sources that produce results asynchronously—such as data streams, paginated API responses, or delayed events—using a clean and declarative syntax instead of relying on manual callback chaining or complex promise management.

The Asynchronous Iteration Protocol

To understand Symbol.asyncIterator, it helps to compare it to standard synchronous iteration:

When an object has a [Symbol.asyncIterator] method, JavaScript marks it as an AsyncIterable.

How for await...of Interacts with the Symbol

The primary consumer of Symbol.asyncIterator is the for await...of loop. When the loop executes, the runtime performs the following steps:

  1. It calls the object’s [Symbol.asyncIterator]() method to retrieve the async iterator instance.
  2. It invokes .next() on the iterator, which returns a Promise.
  3. It awaits the Promise resolution.
  4. If done is false, it assigns value to the loop variable and executes the loop body.
  5. It repeats steps 2–4 until the resolved object has done: true.

Implementing a Custom Async Iterable

You can define [Symbol.asyncIterator] manually on any custom object:

const asyncCounter = {
  max: 3,
  [Symbol.asyncIterator]() {
    let current = 1;
    const max = this.max;

    return {
      async next() {
        if (current <= max) {
          // Simulate an asynchronous operation
          await new Promise((resolve) => setTimeout(resolve, 500));
          return { value: current++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

(async () => {
  for await (const num of asyncCounter) {
    console.log(num); // Logs 1, 2, 3 with a 500ms delay between each
  }
})();

Using Async Generators

Writing manual iterators with boilerplate next() methods can be tedious. JavaScript provides Async Generator Functions (async function*), which automatically implement Symbol.asyncIterator under the hood:

async function* fetchPages(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    const data = await response.json();
    yield data;
  }
}

// Consuming the generator
(async () => {
  const pages = fetchPages(['/api/page/1', '/api/page/2']);
  for await (const page of pages) {
    console.log(page);
  }
})();

In this example, calling fetchPages() returns an AsyncGenerator object that inherently satisfies the async iterable protocol through Symbol.asyncIterator.

Practical Use Cases