What are iterators and generators in JavaScript and what are they used for?
TL;DR
In JavaScript, iterators and generators are tools for managing sequences of data and controlling the flow of execution in a more flexible way.
Iterators are objects that define a sequence and potentially a return value upon its termination. They adhere to a specific interface:
- An iterator object must implement a
next()method. - The
next()method returns an object with two properties:value: The next value in the sequence.done: A boolean that istrueif the iterator has finished its sequence, otherwisefalse.
Here's an example of an object implementing the iterator interface.
Generators are special functions that can pause execution and resume at a later point. They use the function* syntax and the yield keyword to control the flow of execution. When you call a generator function, it doesn't execute completely like a regular function. Instead, it returns an iterator object. Calling the next() method on the returned iterator advances the generator to the next yield statement, and the value after yield becomes the return value of next().
Generators are useful for creating iterators on demand, especially for infinite sequences or complex iteration logic. They can be used for:
- Lazy evaluation – processing elements only when needed, improving memory efficiency for large datasets.
- Implementing iterators for custom data structures.
- Creating asynchronous iterators for handling data streams.
Generator-backed iteration lifecycle
A generator implements the iterator protocol by pausing at each yield and resuming only when its consumer asks for another result.
Every next() call returns an iterator result with value and done; once completed, the iterator cannot resume normal execution.
Iterators
Iterators are objects that define a sequence and provide a next() method to access the next value in the sequence. They are used to iterate over data structures like arrays, strings, and custom objects. The key use cases of iterators include:
- Implementing the iterator protocol to make custom objects iterable, allowing them to be used with
for...ofloops and other language constructs that expect iterables. - Providing a standard way to iterate over different data structures, making code more reusable and maintainable.
Creating a custom iterator for a range of numbers
In JavaScript, we can provide a default implementation for an iterator by implementing [Symbol.iterator]() in any custom object.
Built-in objects using the iterator protocol
In JavaScript, several built-in objects implement the iterator protocol, meaning they have a default @@iterator method. This allows them to be used in constructs like for...of loops and with the spread operator. Here are some of the key built-in objects that implement iterators:
-
Arrays: Arrays have a built-in iterator that allows you to iterate over their elements.
-
Strings: Strings have a built-in iterator that allows you to iterate over their characters.
-
DOM NodeLists
Maps and Sets also have built-in iterators.
Generators
Generators are a special kind of function that can pause and resume their execution, allowing them to generate a sequence of values on-the-fly. They are commonly used to create iterators but have other applications as well. The key use cases of generators include:
- Creating iterators in a more concise and readable way compared to manually implementing the iterator protocol.
- Implementing lazy evaluation, where values are generated only when needed, saving memory and computation time.
- Simplifying asynchronous programming by allowing code to be written in a synchronous-looking style using
yieldandawait.
Generators provide several benefits:
- Lazy evaluation: They generate values on the fly and only when required, which is memory efficient.
- Pause and resume: Generators can pause execution (via
yield) and can also receive new data upon resuming. - Asynchronous iteration: With the advent of
async/await, generators can be used to manage asynchronous data flows.
Creating an iterator using a generator function
We can rewrite our Range example to use a generator function:
Iterating over data streams
Generators are well-suited for iterating over data streams, such as fetching data from an API or reading files. This example demonstrates using a generator to fetch data from an API in batches:
This generator function fetchDataInBatches fetches data from an API in batches of a specified size. It yields each batch of data, allowing you to process it before fetching the next batch. This approach can be more memory-efficient than fetching all data at once.
Implementing asynchronous iterators
Generators can be used to implement asynchronous iterators, which are useful for working with asynchronous data sources. This example demonstrates an asynchronous iterator for fetching data from an API:
The generator function fetchDataAsyncIterator is an asynchronous iterator that fetches data from an API in pages. It yields each page of data, allowing you to process it before fetching the next page. This approach can be useful for handling large datasets or long-running operations.
Generators are also used extensively in JavaScript libraries and frameworks, such as Redux-Saga and RxJS, for handling asynchronous operations and reactive programming.
Summary
Iterators define a standard protocol for traversing data sequences. Generators implement iterators by suspending and resuming at yield; they can also be used to coordinate asynchronous operations and compose data pipelines.
Further reading
- Iterators and generators
- Async iteration and generators
- Iterables
- Explore Iterators and Generators in JavaScript
- Iterators and Generators in JavaScript