Skip to Content
Course content

108: Generators and Iterators in JavaScript

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a project last week where I had to process a massive set of log entries. The problem is that loading a hundred thousand lines into an array just to loop over them is a great way to crash a browser tab or eat up all your server's RAM. I needed a way to say, "Give me the next item when I'm ready for it," rather than "Give me everything right now."

The struggle with manual state

My first instinct was to build a simple "cursor" object. I wanted something that remembered where it left off in the data set. I tried writing it like this:

const logs = ['Log 1', 'Log 2', 'Log 3', 'Log 4'];

const logCursor = {
  position: 0,
  next() {
    if (this.position < logs.length) {
      return { value: logs[this.position++], done: false };
    } else {
      return { value: undefined, done: true };
    }
  }
};

console.log(logCursor.next()); // { value: 'Log 1', done: false }
console.log(logCursor.next()); // { value: 'Log 2', done: false }

This actually works. This is essentially what a JavaScript Iterator is: an object with a next() method that returns an object with value and done. But honestly? It's a pain to write. I have to manually track the position, handle the boundary check, and return that specific object structure every single time. It feels like I'm doing the engine's job for it.

Finding a better way to pause

I remember hearing about Generators, which are basically functions that can be paused and resumed. The magic happens with a * after the function keyword and the yield keyword. Let's try rewriting that log cursor using a generator to see if it cleans up the noise.

function* logGenerator(data) {
  for (const entry of data) {
    yield entry;
  }
}

const gen = logGenerator(['Log 1', 'Log 2', 'Log 3', 'Log 4']);

console.log(gen.next()); // { value: 'Log 1', done: false }
console.log(gen.next()); // { value: 'Log 2', done: false }

Look at that. The yield keyword is doing all the heavy lifting. When the code hits yield, it literally freezes the function's execution state—including all local variables—and spits the value out. When I call next() again, it wakes back up exactly where it left off.

Wait, why bother with the .next() syntax?

You might be looking at this and thinking, "Why would I ever use gen.next() when I can just use a for...of loop?" That's a fair question. The reason is that generators are iterable. Since they follow the iterator protocol (returning that value/done object), they plug directly into JavaScript's built-in loops.

function* idGenerator() {
  let id = 1;
  while (true) { // Yes, an infinite loop!
    yield `user_${id++}`;
  }
}

const ids = idGenerator();

// I can take just what I need without crashing the program
console.log(ids.next().value); // "user_1"
console.log(ids.next().value); // "user_2"

// Or use it in a loop with a break condition
for (const id of ids) {
  console.log(id); 
  if (id === 'user_5') break; 
}

The infinite loop here is actually a feature, not a bug. Because the generator pauses at yield, it doesn't lock up the main thread. It only computes the next value when specifically asked. This is incredibly powerful for things like generating unique IDs, reading a file line-by-line, or implementing a custom pagination system where you don't know how many pages exist until you actually hit the last one.




📋 Practical Task

Building a Smart Batch-Processing Sequence

You are building a system that processes a list of orders, but your API can only handle them in batches of 3. Instead of slicing the array manually, create a generator function called batchProcessor.

Requirements:

  • The function should take an array of items as an argument.
  • It should yield an array containing up to 3 items at a time.
  • It should stop once all items have been yielded.

Test your code with this setup:

const orders = ['Order 1', 'Order 2', 'Order 3', 'Order 4', 'Order 5', 'Order 6', 'Order 7'];
const processor = batchProcessor(orders);

console.log(processor.next().value); // Expected: ['Order 1', 'Order 2', 'Order 3']
console.log(processor.next().value); // Expected: ['Order 4', 'Order 5', 'Order 6']
console.log(processor.next().value); // Expected: ['Order 7']
console.log(processor.next().done);  // Expected: true
Rating
0 0

There are no comments for now.

to be the first to leave a comment.