Javascript Generators made easy
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.
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.