Generators in JavaScript

In JavaScript, generators are a special type of function that can be paused and resumed at specific points during its execution, allowing for the creation of iterable sequences of values.

Generators are defined using the function* syntax, and they use the yield keyword to define points at which the generator should pause and return a value to the caller. When a generator is called, it returns an iterator object that can be used to step through the sequence of values generated by the function.

Summary of how it works

  • Generator functions does not execute immediately when it is called instead it will return an iterable object which can be called at any instance to get the data.
  • The function can be executed on step by step according to the yield expression.
  • We can move to next step using the next() function call and stop at the very next yield statement.
    Every time we execute the next() it will return an object like this

    { value: "some-value", done: false }

    - The value here is the send by yield
    - done represents if the running of the generator got completed.

Example

const studentsArr = ["Rumi", "Rintu", "Akshay"]

// Generator function declaration
function* studentGenerator() {
  for (let stu of studentsArr) {
    yield stu
  }
}

const iterator = studentGenerator()

console.log("Student gen iteration 1", iterator.next())
// OUTPUT: { value: "Rumi", done: false }

console.log("Student gen iteration 2", iterator.next())
// OUTPUT: { value: "Rintu", done: false }

console.log("Student gen iteration 3", iterator.next())
// OUTPUT: { value: "Akshay", done: false }

console.log("Student gen iteration 4", iterator.next())
// OUTPUT: { value: undefined, done: true }

Use cases (React Examples)

Iterating over large datasets: If you have a large dataset that needs to be rendered in a React component, you may want to consider using a generator to lazily load data as it is needed, rather than loading the entire dataset into memory at once.

function* generateItems(data) {
  for (let i = 0; i < data.length; i++) {
    yield <Item key={i} data={data[i]} />;
  }
}

function ItemList({ data }) {
  return <div>{generateItems(data)}</div>;
}

In this example, the generateItems generator function takes an array of data and yields a React component for each item. The ItemList component renders these components using the generator, so only the components that are currently visible in the viewport are actually rendered.

Handling asynchronous operations: Generators can be used to simplify asynchronous operations in React, particularly when dealing with multiple async operations that need to be executed in a specific order. For example:

function* fetchData() {
  const data1 = yield fetch('/api/data1');
  const data2 = yield fetch('/api/data2');
  const data3 = yield fetch('/api/data3');
  return [data1, data2, data3];
}

function MyComponent() {
  const [data, setData] = useState([]);
  useEffect(() => {
    const iterator = fetchData();
    const next = (result) => {
      if (result.done) {
        setData(result.value);
      } else {
        result.value.then((data) => next(iterator.next(data)));
      }
    };
    next(iterator.next());
  }, []);
  return (
    <div>
      {data.map((item, index) => (
        <div key={index}>{item}</div>
      ))}
    </div>
  );
}

In this example, the fetchData generator function performs three async operations in sequence, using the yield keyword to pause execution until each operation is complete. The MyComponent component uses the generator to fetch data and update its state when all three operations are complete.