Understanding JavaScript Generators with Practical Examples
JavaScript generators are powerful functions that allow us to pause and resume function execution, offering an alternative to traditional iteration and asynchronous patterns. Generators are particularly useful for handling sequences, streams of data, and complex control flows in JavaScript applications. This article will cover the basics of generators, provide practical examples, and show how you can use them in a React component.
What is a Generator?
A generator in JavaScript is a function that can pause its execution and resume later. Generators are defined using the function* syntax and return a special type of iterator called a generator object, which uses the yield keyword to pause and resume execution.
function* generatorFunction() {
// code here
}Generators differ from regular functions in two key ways:
- They can be paused with
yield. - They return values incrementally using
next()instead of returning all at once.
Basic Example of a Generator
Let’s start with a simple generator that yields a sequence of numbers.
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
const generator = numberGenerator();
console.log(generator.next().value); // 1
console.log(generator.next().value); // 2
console.log(generator.next().value); // 3
console.log(generator.next().done); // true (no more values)In this example:
- Each call to
next()returns the next value in the sequence. - After all
yieldstatements are exhausted,doneis set totrue.
Practical Example: Generating a Fibonacci Sequence
The Fibonacci sequence is a perfect use case for generators, as it can produce an infinite series of numbers.
function* fibonacciGenerator() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fibGen = fibonacciGenerator();
console.log(fibGen.next().value); // 0
console.log(fibGen.next().value); // 1
console.log(fibGen.next().value); // 1
console.log(fibGen.next().value); // 2
console.log(fibGen.next().value); // 3
console.log(fibGen.next().value); // 5Using Generators for Asynchronous Data Flow
Generators are also helpful for managing asynchronous code because they can yield Promises, enabling more flexible control over async workflows.
Example: Fetching Data in Sequence
Let’s fetch data from two APIs sequentially using a generator:
function* fetchData() {
const userResponse = yield fetch('https://jsonplaceholder.typicode.com/users/1');
const postResponse = yield fetch('https://jsonplaceholder.typicode.com/posts/1');
return { user: yield userResponse.json(), post: yield postResponse.json() };
}
async function runGenerator(genFunc) {
const generator = genFunc();
let result = generator.next();
while (!result.done) {
result = generator.next(await result.value);
}
return result.value;
}
runGenerator(fetchData).then(console.log);Get Frontend Highlights’s stories in your inbox
Join Medium for free to get updates from this writer.
In this example:
fetchDatafetches a user and then a post.runGeneratorcontrols the generator, awaiting each asynchronous request sequentially.
Using Generators in a React Component
Now, let’s look at how to integrate a generator with a React component. Generators can be helpful for managing complex logic, such as generating sequential IDs, cycling through values, or even implementing paginated data fetching.
Example: Unique ID Generator in a React Component
Suppose we want to generate a unique ID for each item added to a list.
import React, { useState } from 'react';
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const ItemList = () => {
const [items, setItems] = useState([]);
const generator = idGenerator();
const addItem = () => {
const id = generator.next().value;
setItems([...items, { id, name: `Item ${id}` }]);
};
return (
<div>
<button onClick={addItem}>Add Item</button>
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};
export default ItemList;In this component:
idGeneratoryields a new ID each timenext()is called.- Each click of the button generates a new unique ID, which is added to the
itemsarray.
Example: Pagination with Generators
Generators are also useful for lazy-loading data. Let’s create a pagination component where the generator yields a set of items each time a button is clicked.
import React, { useState } from 'react';
function* paginatedData(data, pageSize) {
for (let i = 0; i < data.length; i += pageSize) {
yield data.slice(i, i + pageSize);
}
}
const data = Array.from({ length: 100 }, (_, i) => `Item ${i + 1}`);
const PaginatedList = () => {
const [items, setItems] = useState([]);
const [page, setPage] = useState(0);
const generator = paginatedData(data, 10);
const loadMore = () => {
const nextPage = generator.next().value;
if (nextPage) {
setItems(prevItems => [...prevItems, ...nextPage]);
setPage(page + 1);
}
};
return (
<div>
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<button onClick={loadMore} disabled={page * 10 >= data.length}>
Load More
</button>
</div>
);
};
export default PaginatedList;In this component:
paginatedDatais a generator function that yields pages of data.- Each click of the “Load More” button appends the next page of items to the
itemsarray.
When to Use Generators vs. Async/Await
While async/await is generally more popular for handling asynchronous operations, generators offer several unique benefits:
- Control over execution flow: Generators allow you to pause execution at specific points.
- Lazy Evaluation: Generators only calculate values when needed, which is memory-efficient for large or infinite datasets.
- Simplifies Iteration: Generators can simplify iterating over large or complex data structures.
Generators are particularly useful for complex asynchronous flows, lazy-loading data, and handling sequences where you need fine-grained control.









