EasyCoders

Javascript Generators made easy

2019-08-25 Matthew Brimmerjavascript

In our previous article, we discussed Iterators and went over some examples implementing custom ones. Custom iterators can be difficult to implement and require you to create objects that maintain an internal state. Fortunately, javascript provides us an alternative way to implement a custom iterator with a single function called Generators.

Wind generators in a field Photo by Karsten Würth on Unsplash

Anatomy of a Generator

Generators are easy to spot since they require a specific syntax. Generators use the function* (function star) syntax and when called return a type of iterator. Subsequent calls to the Generator, return a new iterator each time (similar to our iterator factory function ) and thus can only be iterated onetime in their lifespan. We can then consume the iterator returned by the generator by calling its next() method until it is exhausted and then it will return done: true. A generator will execute until it reaches a yield statement. Let take a look at a simple generator that iterates an array.

function* generator() {
  yield* [1, 2, 3]
}

const seq = generator()
console.log(seq.next().value)
// => 1
console.log(seq.next().value)
// => 2
console.log(seq.next().value)
// => 3
console.log(seq.next().done)
// => true

An important thing to notice is that we used yield* in our generator. The * allows us to yield an expression or an iterable, e.g. Arrays. Let’s take a look at what would happen if used yield instead of yield*:

function* generator() {
  yield [1, 2, 3]
}

const seq = generator()
console.log(seq.next().value)
// => [1, 2, 3, 4, 5]
console.log(seq.next().done)
// => true

The generator yields the value and then completes by returning done. We can yield multiple values in a generator by using multiple yield statements. Let’s take a look:

function* generator() {
  yield 1
  yield 2
}

const seq = generator()
console.log(seq.next().value)
// => 1
console.log(seq.next().value)
// => 2
console.log(seq.next().done)
// => true

The generator in this case executes each yield when its next function is called and when no more yields are left it returns done: true.

Fibonacci Generator

In the Iterators article, we looked at an example of the Fibonacci sequence implemented as an iterator. We can simplify the implementation with generators. Here is what it looks like:

function* fibonacci(end = 1) {
  let n1 = 1
  let n2 = 1
  let count = 0
  while (count < end) {
    count++
    const current = n2
    n2 = n1
    n1 = n1 + current

    yield current
  }
}

const seq = fibonacci(4)
console.log(seq.next().value)
// => 1
console.log(seq.next().value)
// => 1
console.log(seq.next().value)
// => 2
console.log(seq.next().value)
// => 3
console.log(seq.next().done)
// => true

When we compare the fibonacci generator to the iterator, we notice that the logic is the same, but the overall implementation is much shorter. Another key point is that once our while loop condition returns false the generator returns done: true for us and we don’t have to explicitly do it ourselves. In more complicated iterators, the benefits can be even greater and greatly simplify the implementation.

Async flow control with Generators and Co

We can combine generators with promises and have a nice async flow control that is easy to implement. In this example we are implementing similar to Promise.all(), here it is using co:

const co = require('co')

const fn = co.wrap(function *(){
  // resolve multiple promises in parallel
  var a = new Promise(resolve => {
    setTimeout(() => {
      resolve(1)
    }, 100)
  })
  var b = new Promise(resolve => {
    setTimeout(() => {
      resolve(2)
    }, 100)
  })
  var c = new Promise(resolve => {
    setTimeout(() => {
      resolve(3)
    }, 1000)
  })
 return yield [a, b, c]

})

fn(true)
  .then(function (val) {
    console.log(val)
    // => [1, 2, 3]
  })

The generator won’t resove until the last promise has returned. In this case the third promises takes a second to resolve and once that occurs the generator yields the results to the then function. co as several other use cases that can make async flow control easy. I will note that most use cases are now more suited to async/await, which I will be covering in an upcoming article. As you can see generators are a powerful feature of javascript and can make implementing itearables much easy with less code. Please leave any questions or comments below and as always Happy Coding!

Other articles on Generators

EasyCoders always wants to give you the best resources, so here are some other good resources on generators.

Loading...
EasyCoders - code made easy

Matthew Brimmer our lead instructor is a fullstack developer that has worked building large complex applictaions for a variety of companies in the Atlanta area. He currently works for RentPath on the Rent.com brand. He brings great experience and passion for helping others become successful software developers. Outside of development he enjoys kitesurfing, scuba diving and most other activities on the water.