JavaScript: Understanding Generators

Mosh Hamedani
Jan 6 · 6 min read

In 2015, a rich set of new features was introduced to the JavaScript community. Things like arrow functions, classes and modules quickly became popular and are heavily used in modern JavaScript programming. Some other features are less known and used by developers nowadays. Do you know what generators are? This is exactly what we will learn in this article.

A brief introduction

Before understanding generators, we need to see a few other things first. Generators are all about iteration. So, let’s see how iteration evolved in JavaScript. This way, we can learn what benefits could generators bring when properly used.

Iteration

In JavaScript, arrays are the most common thing people iterate over. So, let’s take them as an example. There are multiple ways of iterating over an array. Here are the four most common:

1) Using the forEach instance method

someArray.forEach(item => console.log(item));

2) Using while loops

let i = 0;
while (i < someArray.length) {
const item = someArray[i];
console.log(item);
i++;
}

3) Using for loops

for (let i = 0; i < someArray.length; i++) {
const item = someArray[i];
console.log(item);
}

4) Using for-of loops

for (let item of someArray) {
console.log(item);
}

It looks clear that the forEach method and the for-of loop are the friendliest options. The other two are more verbose and expect us to deal with indexes manually. The forEach method is defined in the Array’s prototype. The for-of loop allows us to iterate over arrays, maps, sets and even strings. Yes, strings as well:

for (let letter of 'abcdef') {
console.log(letter);
}
//=> 'a'
//=> 'b'
//=> 'c'
//=> 'd'
//=> 'e'
//=> 'f'

Iterators

Any object that implements the iterator protocol is an iterator. The iterator protocol is really simple to understand and adhere to. It expects the object to have a next() method. When called, it should move to the next item in the sequence and return an object. This object must have at least two properties: done and value. The done property is a boolean that indicates whether the iterator is past the end. The value property, as the name suggests, is the value at the current position.

Let’s see a simple example. This function creates iterators with a sequence of squared numbers:

function buildSquaredNumbersIterator(from = 1, to = 10) {
let count = from - 1;
return {
next: () => {
if (count >= to) return { done: true };

count++;
return { value: count * count, done: false };
}
};
}

To create an iterator using the above function, you just need to call it:

const iterator = buildSquaredNumbersIterator(2, 9);
console.log(iterator.next()); //=> { value: 4, done: true }
console.log(iterator.next()); //=> { value: 9, done: true }
console.log(iterator.next()); //=> { value: 16, done: true }
console.log(iterator.next()); //=> { value: 25, done: true }
console.log(iterator.next()); //=> { value: 36, done: true }
console.log(iterator.next()); //=> { value: 49, done: true }
console.log(iterator.next()); //=> { value: 64, done: true }
console.log(iterator.next()); //=> { value: 81, done: true }
console.log(iterator.next()); //=> { done: false }

Naturally, you could simply use a loop to iterate over it:

const iterator = buildSquaredNumbersIterator(2, 9);
let res = iterator.next();
while (!res.done) {
console.log(result.value);
res = it.next();
}

Ok, but… could we use our custom iterator with a for-of loop? Well, the answer is no. To be compatible with for-of loops, an object must adhere to the iterable protocol.

Iterables

To be considered an iterable, an object must implement the iterable protocol. It expects the object to have a method whose key is the value of Symbol.iterator. This method will be called with no arguments and should return an iterator.

Let’s adapt our custom iterator to adhere to this protocol:

function buildSquaredNumbersIterator(from = 1, to = 10) {
let count = from - 1;
return {
next: () => {
if (count >= to) return { done: true };

count++;
return { value: count * count, done: false };
},
[Symbol.iterator]: function() {
return this;
}
};
}

The only difference here is the method with the Symbol.iterator key. But why is it just returning this? This is simple to understand. As we saw, the iterable protocol expects this special method to return an iterator. Note that we’re adding this method to an object that adheres to the iterator protocol. This way, we can simply return this, that will point to the object itself. By doing so, our iterator becomes an iterable. This will make it usable in for-of loops:

const numbers = buildSquaredNumbersIterator(2, 9);for (let n of numbers) {
console.log(n);
}
//=> 4
//=> 9
//=> 16
//=> 25
//=> 36
//=> 49
//=> 64
//=> 81

Generators

Our custom iterable/iterator works really well. However, we could have implemented the exact same functionality in a very simpler way by using ES2015’s generator functions. But before refactoring our iterator, let’s understand what a generator is.

  • Generators are produced by a special type of function, called generator function.
  • A generator is both an iterable and an iterator.
  • Even when you explicitly call a generator function, its instructions are not executed.
  • When the next() method is called, the execution proceeds until a yield instruction is found.
  • The yield instruction returns the current value in the iteration sequence.
  • Once a yield instruction is found and a value is returned, the execution is paused. After that, it waits for a new call to the next() method.
  • This cycle repeats until no more yield instructions are found.
  • The iterator comes to an end.

Here is a simple generator function that yields three values:

function *buildHardcodedValuesGenerator() {
console.log('One');
yield 1;
console.log('Two');
yield 2;
console.log('Three');
yield 3;
console.log('The end');
}
const generator = buildHardcodedValuesGenerator();
console.log('Returned value:', generator.next().value);
//=> 'One'
//=> Returned value: 1
console.log('Returned value:', generator.next().value);
//=> 'Two'
//=> Returned value: 2
console.log('Returned value:', generator.next().value);
//=> 'Three'
//=> Returned value: 3
console.log('Returned value:', generator.next().value);
//=> 'The end'
//=> Returned value: undefined

As you can see, the body of our generator function is not executed when it is first called. It is also clear that every time we call the next() method, the execution proceeds until the next yield is found. When this happens, it pauses the execution and yields a value. When no more yields are found, the next() method returns an object with a value of undefined and a done flag set to true.

Now, let’s see how our iterator could be converted to a generator:

function *buildSquaredNumbersIterator(from = 1, to = 10) {
for (let count = from; count <= to; count++) {
yield count * count;
}
}

This looks a lot simpler than our previous version. We don’t have to handle the internal state. Nor do we need to adhere to some protocol. We just call yield for every value that should be returned. Since generators are iterables, we could iterate over it with a for-of loop as well:

const numbersGenerator = buildSquaredNumbersIterator(4, 11);for (let number of numbersGenerator) {
console.log(number);
}
//=> 16
//=> 25
//=> 36
//=> 49
//=> 64
//=> 81
//=> 100
//=> 121

Conclusion

  • An iterator is any object that adheres to the iterator protocol.
  • The iterator protocol expects an object to have a next() method. This method should return an object with the value at the current position and a done flag.
  • An iterable is any object that adheres to the iterable protocol.
  • The iterable protocol expects an object to have a method whose key is Symbol.iterator. This method should return a valid iterator.
  • A generator function is a special type of function that builds generators and contains their logic.
  • A generator is an object that adheres to both iterator and iterable protocols.

If you are looking to learn JavaScript in depth, I highly recommend Mosh’s JavaScript courses. The link to all of the courses are below:

JavaScript Basics for Beginners

Object-Oriented Programming in JavaScript

The Complete Node JS Course

Mastering React

If you liked this article, share it with others as well!

JavaScript in Plain English

Learn the web's most important programming language.

Mosh Hamedani

Written by

Hi! My name is Mosh Hamedani! I am a passionate and pragmatic software engineer with 18 years of professional experience, specializing in full-stack development

JavaScript in Plain English

Learn the web's most important programming language.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade