Generators in JavaScript

Raymond Chow
3 min readOct 13, 2019

--

Before learning about generators, I highly suggest learning about iterators first. You can read about iterators in my Iterators in JavaScript blog. ES6 introduced generators into the JavaScript language. Generators are a type of iterator that is returned when executing a generator function. A generator function is written with the function* syntax. By executing the next() function of a generator, the generator function executes until it encounters a yield keyword.

Let’s start off with a simple example:

function* generatorExample() {
yield "Hello";
yield "How are you doing?";
yield "This is the end";
}

What happens when we run generatorExample?

const i = generatorExample();

i.next() //{value: "Hello", done: false}
i.next() //{value: "How are you doing?", done: false}
i.next() //{value: "This is the end", done: false}
i.next() //{value: undefined, done: true}

Because generatorExample is a generator function, it returns an iterator. Remember that iterators are objects with a next function. When executing our generatorExample function, we get back an iterator that is stored in our variable i. When we run thenext function that our iterator has the first time, we get back the object: {value: “Hello”, done: false} . When we call the next function again, we get back the object: {value: “How are you doing?”, done: false}. We can think of generator functions as functions that can pause. The way we pause the function is with the yield keyword. Whenever we call the next function, the code in the generator function runs until it hits the yield keyword, which then pauses the execution of the function. To demonstrate this we can add a console.log between our yield values:

function* generatorExample() {
yield "Hello";
console.log("Not yield yet!");
console.log("Still not yielded!");
yield "How are you doing?";
yield "This is the end";
}

const i = generatorExample()

i.next()
//{value: "Hello", done: false}
i.next()
// Not yield yet!
// Still not yielded!
// {value: "How are you doing?", done: false}
i.next()
//{value: "This is the end", done: false}

As we can see, our console.log is not executed until after the second time we call next! This is because the first yield statement paused our generator function from continuing.

By using generators, we can make our iterator code a lot more readable! For example, below we have code for an iterable Range class:

class Range {
constructor(start, end) {
this.start = start
this.end = end;
}

[Symbol.iterator]() {
let [current, end] = [this.start, this.end];
return { //returns the iterator object
next() { //implementing next() method
if (current >= end) {
return { done: true }
} else {
return { value: current++, done: false }
}
}
}
}
}

This is what the class would look like with a generator:

class Range {
constructor(start, end) {
this.start = start
this.end = end;
}
* [Symbol.iterator]() {
let [current, end] = [this.start, this.end];
while(true) {
if(current >= end) return
yield current++;
}
}
}

I highly suggest trying to use generators when creating your own custom iterables! It makes your code a lot more readable in my opinion. I hope this helped you learn a bit about generators and what they are used for!

--

--