Skip to content

When and Where to Use Generator Functions - Practical Use Cases

Posted on:December 1, 2023 at 12:00 AM (3 min read)

Introduction

Now that we’ve explored the mechanics of generator functions, it’s essential to understand where they can be utilized best. Despite their power, they are not a one-size-fits-all solution. But, for certain scenarios, they are indeed a game-changer. Let’s dive into some practical use cases of generator functions.

Handling large data sets

One of the most common use cases for generator functions is when working with large datasets you don’t want to hold in memory all at once. For example, if you need to process a massive array of data, instead of loading the entire array into memory, you can use a generator to yield each item one at a time.

function* dataGenerator(dataArray) {
  for (let i = 0; i < dataArray.length; i++) {
    yield dataArray[i];
  }
}

const data = [...Array(1000000).keys()]; // Large data array
const generator = dataGenerator(data);

let currentItem = generator.next();
while (!currentItem.done) {
  console.log(currentItem.value);
  currentItem = generator.next();
}

In this example, the dataGenerator function yields each item from the array one at a time, significantly reducing memory usage.

Managing asynchronous flow

As we mentioned earlier, generators can be handy for handling asynchronous operations. With generators, we can write asynchronous code that looks synchronous, thus improving code readability and maintainability.

Consider a case where multiple API calls must be made in a specific order. Using promises or callbacks can lead to deeply nested code (callback hell), but we can simplify this with generators.

function* fetchSequentialData() {
  const users = yield fetch("/api/users").then(res => res.json());
  const posts = yield fetch(`/api/users/${users[0].id}/posts`).then(res =>
    res.json()
  );
  return posts;
}

const generator = fetchSequentialData();

generator
  .next()
  .value.then(data => generator.next(data).value)
  .then(data => generator.next(data));

In this example, fetchSequentialData fetches user data, waits for it to resolve, and then fetches post data for the first user. This process occurs sequentially without deeply nesting the code.

Infinite data structures

Generators are also great for creating infinite data structures. Suppose you’re creating a game and need an endless supply of enemy characters. You could use a generator function to create enemies as you need them without any predefined limit.

function* enemyGenerator() {
  let id = 0;
  while (true) {
    yield { id: id++, type: "enemy", hp: 100 };
  }
}

const enemies = enemyGenerator();

console.log(enemies.next().value); // { id: 0, type: 'enemy', hp: 100 }
console.log(enemies.next().value); // { id: 1, type: 'enemy', hp: 100 }
// ... and so on, infinitely.

In this example, enemyGenerator it will create a new enemy each time it’s called and will do so indefinitely.

In conclusion, generator functions are a powerful addition to JavaScript. While they may not be the right tool for every job, they truly shine in certain scenarios like managing large data sets, asynchronous flow, or creating infinite data structures.