DEV Community

arenasbob2024-cell
arenasbob2024-cell

Posted on • Originally published at viadreams.cc

JavaScript Generators and Iterators: A Practical Guide

Generators are one of JavaScript's most underused features. Once you understand them, you'll find uses everywhere.

The Core Concept

A generator function can pause and resume. It yields values one at a time.

function* counter() {
  let n = 0;
  while (true) {
    yield n++; // Pause here, return n, resume on next call
  }
}

const gen = counter();
gen.next(); // { value: 0, done: false }
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
// Infinite sequence, no memory issues
Enter fullscreen mode Exit fullscreen mode

The * makes it a generator. yield is the pause point.

Infinite Sequences

function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

// Take first 10 Fibonacci numbers
function take(gen, n) {
  const result = [];
  for (const value of gen) {
    result.push(value);
    if (result.length === n) break;
  }
  return result;
}

take(fibonacci(), 10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Enter fullscreen mode Exit fullscreen mode

Lazy Evaluation

Process huge datasets without loading them all into memory:

function* processLargeFile(lines) {
  for (const line of lines) {
    if (line.startsWith('#')) continue;  // Skip comments
    const [key, value] = line.split('=');
    yield { key: key.trim(), value: value.trim() };
  }
}

// Only processes lines as needed - doesn't load 10GB into memory
for (const entry of processLargeFile(readFileLines('huge-config.txt'))) {
  if (entry.key === 'TARGET_KEY') {
    console.log(entry.value);
    break; // Stops reading the file
  }
}
Enter fullscreen mode Exit fullscreen mode

Two-Way Communication

You can send values INTO a generator:

function* calculator() {
  let result = 0;
  while (true) {
    const input = yield result; // Send current result out, wait for input
    result += input;
  }
}

const calc = calculator();
calc.next();      // Start: { value: 0, done: false }
calc.next(10);    // Add 10: { value: 10, done: false }
calc.next(5);     // Add 5: { value: 15, done: false }
calc.next(-3);    // Subtract 3: { value: 12, done: false }
Enter fullscreen mode Exit fullscreen mode

Async Generators

Combine generators with async/await for streaming data:

async function* streamPages(url) {
  let page = 1;
  while (true) {
    const response = await fetch(`${url}?page=${page}`);
    const data = await response.json();

    if (data.items.length === 0) return;

    yield* data.items;    // Yield each item individually
    page++;
  }
}

// Process API results as they stream in
for await (const user of streamPages('/api/users')) {
  await processUser(user);
}
Enter fullscreen mode Exit fullscreen mode

Generator-Based State Machine

function* trafficLight() {
  while (true) {
    yield 'green';   // 30 seconds
    yield 'yellow';  // 5 seconds
    yield 'red';     // 30 seconds
  }
}

const light = trafficLight();
setInterval(() => {
  const { value } = light.next();
  updateLight(value);
}, 5000);
Enter fullscreen mode Exit fullscreen mode

Real-World: Redux-Saga Pattern

Generators power redux-saga for complex async flows:

function* fetchUserSaga(action) {
  try {
    yield put({ type: 'FETCH_START' });
    const user = yield call(fetchUser, action.userId); // Pausable async call
    yield put({ type: 'FETCH_SUCCESS', payload: user });
  } catch (error) {
    yield put({ type: 'FETCH_ERROR', error });
  }
}
Enter fullscreen mode Exit fullscreen mode

The testability is excellent — you can test each yield step in isolation without making real API calls.

Use the JavaScript Minifier to optimize your generator code for production.


Full generators guide at viadreams.cc/en/blog/javascript-generators-guide

Top comments (0)