DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

JavaScript Generators and Async Generators: Lazy Sequences, State Machines, and Streaming

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Generators implement this protocol automatically.

Top comments (0)