Day 5: Generators and Iterators

Remember when you learned something fundamental that changed the way you thought about everything? Maybe it was LISP and suddenly everywhere you saw Lists. Maybe it was discovering we are stardust, when you ran out into the street shaking people saying "dude we are all made of the same stuff!". Or maybe it was that time you discovered that the thing on the end of pans is meant to hold a spatchulla or a spoon. Or maybe it was that time you learned how to spell spatula correctly.

Generators are the JavaScript equivalent of stardust and spatula holders. They change the way you do JavaScript for forever. Once you learn them you will want to turn everything into a generator. You will want to use them for every thing. They are that good! White chocolate chip macadamia nut cookie good. Cuddled in a blanket with your favorite human in front of fire on Christmas good.

Generators are iterables that you yield values from using a special keyword called yield. Every time you yield a value the execution of the generator is paused until you ask for another value. It sounds incredibly simple but from great simplicity comes great power. (Rich Hickey will tell his nephew that right before he dies).

Generators look like this:

const catGenerator = function* () {
  yield 'Sweatshirt'
  yield 'Cat Damon'
  yield 'Sassy'
}

const generateCats = catGenerator()
console.log(generateCats.next().value)
console.log(generateCats.next().value)
generateCats.next().value
Try in REPL

Generators are useful for all sorts of flow control type tasks. You can use them to manage async code and make it look and feel synchronous. You can use them for lazy evaluation and infinite sequences. You can use them for generating things like actions and side effects.

Asynchronous Code With Generators

It wasn't too many eons ago that using generators to manage Promises were all the rage. That is in, JS time, in real time it was like four months ago. Humans realized that for the 90% use case generators were overcomplicated, so they came up with async/await and we have been using it ever since. Generators for asynchronous code fell into disuse and before long only the great sages of old knew of their power. We can still learn a lot from the old ways though.

Generators make handling async code a lot cleaner. It isn't entirely clear how to do it though. The first step is to craft a generator that yields a promise. It might look like this:

const willEventuallyCat = () => {
  const cat = {
    name: 'Ferguson'
  }

  const promise = new Promise((resolve) => {
    setTimeout(() =>{
      resolve(cat)
    }, 100)
  })

  return promise
}

const catGenerator = function* () {
  yield willEventuallyCat()
}

const generateCats = catGenerator()
generateCats.next().value
Try in REPL

This generator yields a promise. But that isn't entirely what we want, we want a generator that eventually yields a cat. How do we wait for the promise to finish and then yield the value? Remember that we can yield values into a generator? All we have to do is wait for that promise outside of the generator and then yield the result into the generator when it is ready:

const willEventuallyCat = () => {
  const cat = {
    name: 'Ferguson'
  }

  const promise = new Promise((resolve) => {
    setTimeout(() =>{
      resolve(cat)
    }, 100)
  })

  return promise
}

const catGenerator = function* () {
  const cat = yield willEventuallyCat()
  yield cat
}

const generateCats = catGenerator()
generateCats.next().value.then((cat) => {
  console.log(generateCats.next(cat).value)
})
Try in REPL

In a real world scenario you can generalize this (or use a library) so that all promises can be handled, not just this particular one.

Iterators

Have you ever wanted to loop over something? Iterators make it easier to loop over all sorts of things. You can wrap any data with the iterable protocol and JS will give you native tools for iterating over it. An object is an iterable when it implements a next function that takes zero arguments and returns an object with the following shape

{
  done: false, // whether or not the iterator is done
  value, // the value returned
}

JavaScript let's you loop over iterables using for-of loops. Most core data structures support for-of; Arrays, Maps, Sets and more. Looping over an iterable looks like this:

const cats = ['Ferguson', 'Grumpy Cat', 'Snowball']
for (const cat of cats) {
  console.log(cat)
}
Try in REPL

It is important to note though that most of the core data structures do not implement a next function. While they are iterable, you cannot step through them using next. You must wrap them up first:

const makeIterator = (array) => {
  let nextIndex = 0

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

const cats = makeIterator(['Ferguson', 'Grumpy Cat', 'Snowball'])

console.log(cats.next().value)
console.log(cats.next().value)
console.log(cats.next().value)
cats.next()
Try in REPL

Congrats you just made your first iterable! Let's make another one, this time as an object:

const cats = {
  data: ['Ferguson', 'Grumpy Cat', 'Snowball'],
  currentPosition: 0,
  next() {
    return this.currentPosition < this.data.length ?
      { value: this.data[this.currentPosition++], done: false } :
      { done: true }
  }
}

console.log(cats.next().value)
console.log(cats.next().value)
console.log(cats.next().value)
cats.next()
Try in REPL

And we are done! Onto day 6!