Generators allow you to run asynchronous code as synchronous code, among other things…
What does function* means?
First, let me show you how a generator looks like…
Wow! What’s that, right?
Is not as complicated as it looks, but before talking about generators specificaly, we need to understand something called Iteration Protocols.
- It refers to all those structures of data which we can iterate over, in order to get their own content (Strings, Arrays, Typed arrays, Maps, Sets).
- In order to accomplish the Iterable Protocol, the data structure has to have a property key called Symbol.iterator.
- The execution of this Symbol.iterator returns an iterator Object, who respects the iterator protocol (will see in a minute).
- IMPORTANT: Plain objects are not iterables.
- It has to have a next() method.
- The execution of this method returns an object with two properties:
_ Value: contains the value of the current iteration.
_ Done: contains a boolean indicating whether we’re on last iteration or not.
- IMPORTANT: After the last iteration, value will be undefined.
Methods to work with iterators
Now it’s time to talk about some native methods we can work with, in order to get the data inside the object. Let’s see some of them:
- For of: similar to foreach, it allows us to iterate over the data in order to get each element as an independent variable.
- Spread operator: It allows us to get the data from an array or any iterable.
Generators: What are they?
A generator is a function that can be started or paused for the programmer who is writing the generator. It’s execution returns an object that complies with the iterator protocol.
We can define a generator as a sugar sintactic way to write iterators.
Let’s check the syntax again:
What do we have here? Let’s go step by step.
Then we have the generator’s name, nothing weird there. Is just to provide a name to the function, in order to call it later.
But inside the body of the generator, what do we have there?
Yield? What is that about?
When we see a yield instruction like the one in the example, we have to know that our function execution will stop on that word, and the interpreter will return us the value to the right of the yield.
Yield is a word you just can use inside a generator.
- Remember we were talking about iteration protocols and how an iterator can be used to go through all the values inside it by just running the next() method?
- Remember we just said that a generator function returns an iterator object?
Well, we can combine this knowledge to say that every time we run the next() method of a generator, it will stop on a YIELD command and return whatever value we want.
In a generator function you can have as many yield commands as you want. All of them will define the amount of times you can execute the next() method on your iterator object.
Next() method with parameters
Now you may wonder, can we pass parameters to the next() method? Well, to answer that we can do some kind of exercise. First, let’s assign a yield command to a variable, inside our generator function.
Anyone looking at this code may think that yield command is being assigned to fromSecondNext variable. But what is truly happening is that the yield command will stop the execution and return the value at the right of the command.
This way, the second execution of next() command will assign anything we pass as parameter into the fromSecondNext variable.
Once we run the command, the second yield will return the argument passed.
This may seem a bit useless if we see this basic example, but if we think about the possibility of working with the parameter inside the generator function, the utilities are infinites.
Nesting generator functions
Let’s say we have a really big generator, and we want to do some refactoring over it, in order to clarify and improve the code.
Let’s say now that we need to split our big generator into multiple generators, and then start calling them in a secuencial order, just to replicate the previous big generator.
We can totally do this, by using the y_ield*_ command. This command is very similar to the y_ield_ command, but is only used to call another generator inside a generator. Let’s see an example:
This way we have a better code, with a decoupled logic in each one of our generators.
Now, let’s picture we want to call our gen2 function, from another place. We only need to do
And this way we can easily call a generator with params.
Calculating factorial with a nested recursive generator
Let’s play a bit, just for fun. Let’s build a generator that will help us to find a factorial of any number.
Factorial: the results of multiplying all the previous numbers of an specific natural number.
Example: 4! === 4*3*2*1 === 24
The factorial of 4 (4!) is 24.
Do you think you understood generators? That’s great! Could you write an example like this one, but showing the fibonacci sequence on it?
Fibonacci: starting with cero and one, the following number is the result of adding the previous two numbers.
WARNING! The result of previous task is below…
And that’s it! We’ve reached the end of the article about generators and iteration protocols!
Thanks for reading it!