Iterators & Generators

Iterators & Generators

A method capable of performing the same action on every item in a collection.

Iterators

In computer science, an iterator is an object (like a pointer) that enables a programmer to traverse through elements inside a container.

For example: Imagine a coffee shop, and imagine they have a line of customers which is our list in this case. They have asked us to calculate the price for each drink based on the number of espresso shots.

const orders = [
  { Name: 'Jon', espressoShots: 3 },
  { Name: 'Tiki', espressoShots: 2 },
  { Name: 'Devan', espressoShots: 5 },
];

By definition iteration allows us to traverse through orders and calculate a price. In JS you would more than likely use a for-each loop like so.

orders.forEach((order) => {
    return 1.5 * order.espressoShots;
    // -> 7.5, 5, 12.5
  });

This is possible because orders is an iterator. An iterator in JS has an next() method that returns an object with done and value properties. Done signifies if there are any more iterations. Value contains the value of the current object. JS's forEach function first checks the done property and the loop is terminated if done equals true. forEach returns an element "order" in this case with the object and its properties. In other languages like C iterators have next() and hasNext() methods. Calling next without checking if hasNext() will in most cases cause the program to crash. Lets look at an example of how to create our own iterator in JS.

code is not in short hand for clarity and understanding.

function makeIterator(array) {
  let nextIndex = 0;

  return {
    next() {
      if (nextIndex < array.length) {
        return {
          value: array[nextIndex++],
          done: false,
        };
      } return {
        done: true,
      };
    },
  };
}

const it = makeIterator(orders);
console.log(it.next()); //x4 -->

// Results from calling it.next() four times.
{
  value: {
    Name: 'Jon',
    espressoShots: 3
  },
  done: false
} {
  value: {
    Name: 'Tiki',
    espressoShots: 2
  },
  done: false
} {
  value: {
    Name: 'Devan',
    espressoShots: 5
  },
  done: false
} {
  done: true
}

Notice the last call just returns done: true without a value and this is because for this iterator there are no more elements in the array.
In summary Iterators is just a way for us to go through each array value and do some calculation.

Generators

In the above example we constructed a custom Iterator and although we can do this, we can use Generators to create custom iterators. Think about generators as little factories for iterators. In JS we can define a Generator function using the function* syntax.

function* orderTotal() {
  const noOrders = Math.random() > 0.60;
  
  while (true) {
    if (noOrders) return;
    
    // numEspresso returns an integer between 1-10
    const numEspresso =
      Math.floor(Math.random()*Math.floor(10));
      
    yield 1.5 * (numEspresso);
  }
}

Defining a Generator function is infinite in nature. In this example we create a stopping point so that when next is called { done: true } is returned. The generator creates an iterator with the total already calculated or in layman terms an array of calculated values in which you can iterate through. The yield keyword essentially pauses the generator function until next is called.

In summary a Generator creates a custom Iterator and an Iterator allows you to traverse an array of values one at a time.