JavaScript generators are a unique feature that can produce a sequence of values on demand — pausing execution at each yield and resuming when the caller requests the next value. Firefox has full generator and iterator protocol support, including async generators for streaming data.
Basic Generator Syntax
function* counter(start = 0) {
while (true) {
yield start++;
}
}
const gen = counter(10);
gen.next(); // { value: 10, done: false }
gen.next(); // { value: 11, done: false }
gen.next(); // { value: 12, done: false }
// Infinite generator — never exhausts
function* declares a generator function. yield pauses execution and returns a value. gen.next() resumes until the next yield.
Finite Generators
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
function* take(gen, n) {
for (const value of gen) {
yield value;
if (--n === 0) return;
}
}
// First 10 Fibonacci numbers
const first10 = [...take(fibonacci(), 10)];
// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
yield* — Delegate to Another Generator
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
function* combined() {
yield* range(1, 3); // Yields 1, 2, 3
yield 'middle';
yield* range(7, 9); // Yields 7, 8, 9
}
console.log([...combined()]);
// [1, 2, 3, 'middle', 7, 8, 9]
Generators as Iterators
Generators implement the iterator protocol — they work anywhere an iterable is expected:
function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
// for...of
for (const n of range(0, 10, 2)) {
console.log(n); // 0, 2, 4, 6, 8, 10
}
// Spread
const evens = [...range(0, 20, 2)]; // [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
// Destructuring
const [first, second, third] = range(1, 100);
// first=1, second=2, third=3
// Array.from
Array.from(range(1, 5)); // [1, 2, 3, 4, 5]
Two-Way Communication
Generators can receive values via next(value):
function* accumulator() {
let total = 0;
while (true) {
const value = yield total; // Receives value from next()
if (value === null) return total; // Terminate
total += value;
}
}
const acc = accumulator();
acc.next(); // { value: 0, done: false } — start
acc.next(10); // { value: 10, done: false }
acc.next(25); // { value: 35, done: false }
acc.next(5); // { value: 40, done: false }
acc.next(null); // { value: 40, done: true } — terminate
Practical Use Cases
Lazy Evaluation of Large Datasets
function* filterMap(iterable, filter, map) {
for (const item of iterable) {
if (filter(item)) yield map(item);
}
}
function* chunk(iterable, size) {
let chunk = [];
for (const item of iterable) {
chunk.push(item);
if (chunk.length === size) {
yield chunk;
chunk = [];
}
}
if (chunk.length > 0) yield chunk;
}
// Process 1 million items without loading them all into memory
const processed = filterMap(
hugeDataset(), // generator that yields items lazily
item => item.active,
item => ({ id: item.id, name: item.name })
);
// Process in chunks of 100
for (const batch of chunk(processed, 100)) {
await processBatch(batch);
}
Unique ID Generator
function* idGenerator(prefix = 'id') {
let n = 0;
while (true) {
yield `${prefix}-${++n}`;
}
}
const ids = idGenerator('user');
ids.next().value; // 'user-1'
ids.next().value; // 'user-2'
State Machine
function* trafficLight() {
while (true) {
yield 'green';
yield 'yellow';
yield 'red';
}
}
const light = trafficLight();
light.next().value; // 'green'
light.next().value; // 'yellow'
light.next().value; // 'red'
light.next().value; // 'green' (cycles)
Async Generators
Async generators combine generators with async/await — perfect for streaming APIs:
async function* fetchPages(url) {
let nextUrl = url;
while (nextUrl) {
const response = await fetch(nextUrl);
const data = await response.json();
yield data.items; // Yield each page's items
nextUrl = data.nextPage; // null when done
}
}
// Consume with for await...of
for await (const page of fetchPages('/api/items')) {
console.log('Got page:', page.length, 'items');
displayItems(page);
}
Streaming AI Response
async function* streamTokens(prompt) {
const response = await fetch('/api/ai/stream', {
method: 'POST',
body: JSON.stringify({ prompt }),
headers: { 'Content-Type': 'application/json' },
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value, { stream: true });
for (const line of text.split('\n')) {
if (line.startsWith('data: ')) {
const token = JSON.parse(line.slice(6)).token;
if (token) yield token;
}
}
}
}
// Display tokens as they arrive
let output = '';
for await (const token of streamTokens('Tell me a story')) {
output += token;
display(output);
}
The Iterator Protocol
Any object with a [Symbol.iterator]() method that returns { next() } is iterable:
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
return current <= end
? { value: current++, done: false }
: { value: undefined, done: true };
}
};
}
}
const r = new Range(1, 5);
console.log([...r]); // [1, 2, 3, 4, 5]
for (const n of r) console.log(n);
Generators implement this protocol automatically.
Top comments (0)