Generators earn their place in a few real situations.
1. An ID generator
function* idGenerator(prefix = "id") {
let n = 1;
while (true) {
yield `${prefix}-${n++}`;
}
}
const ids = idGenerator("user");
console.log(ids.next().value); // → user-1
console.log(ids.next().value); // → user-2
console.log(ids.next().value); // → user-3
The counter’s state is kept inside the generator, with no global variable.
2. Splitting data into pages
function* chunk(items, size) {
for (let i = 0; i < items.length; i += size) {
yield items.slice(i, i + size);
}
}
const pages = [...chunk([1, 2, 3, 4, 5, 6, 7], 3)];
console.log(pages); // → [[1, 2, 3], [4, 5, 6], [7]]
3. A lazy pipeline
Because generators produce one value at a time, you can chain steps that only do as much work as needed.
function* naturals() {
let n = 1;
while (true) yield n++;
}
function* filter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) yield item;
}
}
function* map(iterable, fn) {
for (const item of iterable) yield fn(item);
}
function* take(iterable, count) {
if (count <= 0) return;
for (const item of iterable) {
yield item;
if (--count === 0) return;
}
}
const evenSquares = take(map(filter(naturals(), n => n % 2 === 0), n => n * n), 4);
console.log([...evenSquares]); // → [4, 16, 36, 64]
Only as many numbers are produced as needed to get four results. (Newer JavaScript also has built-in iterator helpers for this, see Recent Features.)
4. Walking a tree
const menu = { name: "Home", children: [
{ name: "Products", children: [{ name: "Books", children: [] }] },
{ name: "Contact", children: [] }
] };
function* walk(node) {
yield node.name;
for (const child of node.children) {
yield* walk(child);
}
}
console.log([...walk(menu)]); // → ["Home", "Products", "Books", "Contact"]
5. Async generators: streams of data
An async function* can await between values, and you loop over it with for await...of. This fits paged APIs and streams.
const wait = ms => new Promise(r => setTimeout(r, ms));
async function* pages() {
for (let page = 1; page <= 3; page++) {
await wait(100); // pretend to fetch a page
yield { page, items: [`item ${page}a`, `item ${page}b`] };
}
}
async function main() {
for await (const result of pages()) {
console.log("page", result.page, result.items.length);
}
}
main();
When to use a generator
- Lazy or infinite sequences.
- Walking nested data.
- Hiding the state of a process (an ID counter, a paginator).
- Making a class iterable with little code.
When an array is small and you will use all of it, plain arrays are simpler. Do not use generators just because they are interesting.