Iterators and generators are both interesting JavaScript features. And even more so when you use them together. In this blog post, let’s brush up on our understanding of the generators and iterators, and see how we can combine them to write elegant JavaScript code.
Iterator
Put simply, iterator is a symbol that you can use to make any object iterable. Iterable objects can be iterated using for…of loop. Firstly, let’s go over what the symbols are.
Symbol
Symbol is a type of primitive that is guaranteed to be unique. One of its primary purposes is to be used as key for dictionary collections (maps or plain objects). Also, the fact that it’s always unique makes it a good candidate for other interesting use cases. Now let’s explore what I mean by symbol being truly unique:
|
|
The above code will print:
String Unique is not equal to Symbol(Unique)
Symbol(Unique) is not equal to Symbol(Unique)
First if
compares the stringnotSymbol
with a value of Unique
to a symbol instance with the description value of Unique
. As you can see, these two variables are not equal. A more interesting case is the second if
, where we’re comparing two symbols that might seem identical at first. However, even though both symbols have the same description value, they are not equal. Description is used solely to improve the readability of our code.
Defining iterators and iterables
JavaScript providesSymbol.iterator
static symbol that we can use to make any object iterable. Let’s take a look at this example:
First let’s define a class that we’ll be iterating over:
|
|
PetShelter
is a simple class that contains an array of objects called pets
. Below our pets
property, we add another property using Symbol.iterator
as a key. We assign a function to it which returns a new instance of the PetIterator
.
Now let’s see what the definition of PetIterator
looks like:
|
|
PetIterator
is just another class that contains only one method – next
. Every iterator is required to have the next
method. Also, this method must return an object that can contains two properties: done
and value
. You might’ve guessed what these properties are used for:
done
is a boolean property that indicates whether or not we’ve reached the end of the iteration.value
is used to return a value of the currently iterated item.
Using them together
Now that we have our iterable and iterator, let’s see how we can use them together:
|
|
Quite simple right? The for...of
loop automatically created and consumed a new instance of the iterator provided by the PetShelter
class.
The above code will print the following to the console:
{ pet: { type: 'cat', name: 'Emma' } }
{ pet: { type: 'dog', name: 'Bubbles' } }
{ pet: { type: 'dog', name: 'Hank' } }
{ pet: { type: 'cat', name: 'Leo' } }
Generator
Generators are one of the new syntax features introduced with EcmaScript 2015 specification. Just like async
, generators can be used to control the execution flow of the program by pausing and resuming it as the client sees fit. Let’s take a look at an example of how we would define a generator:
|
|
As you can see, generators are just regular functions with a couple of minor differences:
- In order to define a generator you must put
*
after thefunction
keyword. yield
can be used inside of the generator to pause the execution. Also, any value after theyield
keyword is passed back to the client that invokes the generator.
Now let’s see how we can use our generator:
|
|
Here’s the output of the code above:
Hi
What's your name?
I'm done here
Few things to take a note of here:
- Calling
generatorSample()
does not run the generator function, but returns a new instance of the generator instead. - Once the code inside of our generator encounters
yield
, it stops. And we use thenext
method of the generator to proceed with the execution.
Here’s what the structure of the next
object looks like:
|
|
Using generators to create iterators
Looks familiar? Hopefully now you’re starting to see a connection between iterators and generators. Frankly, one of the main reason generators were introduced with ES 2015 was to make the process of creating iterators much easier. In fact, all we have to do to turn ourPetShelter
into an iterable is make [Symbol.iterator
] function return a new instance of a generator. Now let’s see how we can accomplish just that:
|
|
Just like in the earlier example, the above code will produce the following output:
{ pet: { type: 'cat', name: 'Emma' } }
{ pet: { type: 'dog', name: 'Bubbles' } }
{ pet: { type: 'dog', name: 'Hank' } }
{ pet: { type: 'cat', name: 'Leo' } }
As you can see, we no longer need to define a separate class for our PetIterator
. All we had to define was a generator that uses a for
loop to iterate over the pets
property. Next, we adjusted the [Symbol.iterator]
function to return a new instance of the PetIterator
generator instead.
We were able to use the generator in place of the iterator because generators already have a method called next
that returns an object with done
and value
properties.
And that’s it for this post! Here we briefly went over the basics of iterators and generators and how you can use them to easily create iterable objects. I hope you found this post useful.
If you’d like to get more web development, React and TypeScript tips consider
following me on Twitter,
where I share things as I learn them.
Happy coding!