Introduction
If you have been struggling with JavaScript iterators and searching about JavaScript generators, you are in the right place. In this article, we will take a look at JavaScript generators and how they are different from iterators. Moreover; we will go in-depth with its methods and see some tips when using function generators on an arrow function and inside an object.
Background
It’s good to have a basic understanding of iterators, one reason is to recognize the differences between iterators and generators. In such a case, you can read
here or you can Google search. However; if this doesn’t apply to you, you can skip the reading part and proceed to the next section.
What is a JavaScript Generator?
Great! You have probably read the recommended reading material above before jumping into this section. And, hopefully, that helped and I want to say: “Thank you”. OK, then let’s get started.
In my opinion, JavaScript custom iterators are useful when utilized but it demands careful and thorough programming because of its internal state. Thereby, we have an alternative, JavaScript generator, to allow us (JavaScript programmers) to define a single function whose execution is continuous.
Things to remember about JavaScript Generators
- Appears to be a normal function but behaves like an iterator.
- It is written using the function* syntax.
- Invoking a generator function doesn’t execute its body immediately, but rather returns a new instance of the generator object (an object that implements both, iterable and iterator protocols).
- It is a function that returns multiple values one by one.
- It can be paused and resumed. This is possible through the usage of the yield keyword.
Let's see an example below.
-
-
-
-
-
- function* myFavoriteFruits() {
-
- let grapeFruit = yield "Grapefruit",
- pineApple = yield "Pineapple",
- apple = yield "Apples",
- mango = yield "Mango";
- }
Now, that we have seen how to declare a generator. Why not invoke the function generator? Finally, check and see the returned object.
Let’s see an example below.
-
-
-
- var mygenerator = myFavoriteFruits();
-
-
- console.log(mygenerator);
Output
As you can see, the myFavoriteFruits (generator function) returns an instance of the generator object. Moreover, this object implements both iterable and iterator protocols.
JavaScript Generator Instance Methods
The JavaScript generator has 3 instance methods,
The next() method
When the next() method of the generator object executes, it looks for the function’s body until the yield keyword is encountered. Thereby, it returns the yielded value and pauses the function. Again, when the next() method is invoked it resumes the execution, and then returns the next yielded value.
Lastly, the method next() has two properties: value and done.
- value property is the returned value by the iterator.
- done property is true when the generator function doesn’t yield any more value.
Let’s see an example below.
- function* myFavoriteFruits() {
-
- let grapeFruit = yield "Grapefruit",
- pineApple = yield "Pineapple",
- apple = yield "Apples",
- mango = yield "Mango";
-
-
- console.log(grapeFruit, pineApple, apple, mango);
- }
-
- var mygenerator = myFavoriteFruits();
-
-
- console.log(mygenerator.next());
-
-
- console.log(mygenerator.next());
-
-
- console.log(mygenerator.next());
-
-
- console.log(mygenerator.next());
-
-
- console.log(mygenerator.next());
Another thing to remember on the next() method is that we can pass an optional argument to it. This argument becomes the value returned by the yield statement, where the generator function is currently paused.
To go back to the previous example, on line 9 we have this statement: console.log(grapeFruit, pineApple, apple, mango); which resulted in undefined undefined undefined undefined. To fix this, let’s see an example below.
- function* myFavoriteFruits() {
-
- let grapeFruit = yield "Grapefruit",
- pineApple = yield "Pineapple",
- apple = yield "Apples",
- mango = yield "Mango";
-
-
- console.log(grapeFruit, pineApple, apple, mango);
- }
-
- var mygenerator2 = myFavoriteFruits();
-
-
- console.log(mygenerator2.next());
-
-
- console.log(mygenerator2.next("Grapefruit"));
-
-
- console.log(mygenerator2.next("Pineapple"));
-
-
- console.log(mygenerator2.next("Apples"));
-
-
- console.log(mygenerator2.next("Mango"));
The return() method
The return() method can end the generator function before it can yield all the values. Moreover, this method takes an optional argument and acts as the final value to return.
Let's see an example below.
- function* beKindToPeople() {
-
- yield "Be mindful";
- yield "Don't discriminate";
- yield "Buy someone a coffee";
- }
-
- var myKindnessActGenerator = beKindToPeople();
-
-
- console.log(myKindnessActGenerator.next().value);
-
-
- console.log(myKindnessActGenerator.next().value);
-
-
- console.log(myKindnessActGenerator.return("Consider kindness before you speak"));
-
-
- console.log(myKindnessActGenerator.next().done);
The throw() method
This method accepts the exception to be thrown. Thereby, you can manually trigger an exception inside the generator function.
Let us see an example below.
- function* howToBeHappy() {
-
-
- try {
- yield "Treat yourself like a friend";
- } catch (error) {
- console.log("Always be happy 1st try");
- }
-
- try {
- yield "Challenge your negative thoughts";
- } catch (error) {
- console.log("Always be happy 2nd try");
- }
-
- try {
- yield "Choose your friends wisely";
- } catch (error) {
- console.log("Always be happy 3rd try");
- }
- }
-
- var myHappyGenerator = howToBeHappy();
-
-
- console.log(myHappyGenerator.next().value);
-
-
-
-
-
- console.log(myHappyGenerator.throw("Exception thrown but still be happy").value);
-
-
-
-
-
- console.log(myHappyGenerator.throw("Exception thrown but still be happy").value);
-
-
-
-
-
- console.log(myHappyGenerator.throw("Exception thrown but still be happy").done);
The “yield*” Keyword Inside The Function Generator
This expression is used to delegate to another generator or iterable object. Hence, it iterates it to yield its values.
Let’s see an example below.
- function* respectSubordinates() {
-
- yield "Know their strengths and weaknesses";
- yield "Respectful to others."
- }
-
- function* showLove() {
-
- yield "Listen";
- yield* ['acts of service', 'encourage people', 'quality time'];
- yield* respectSubordinates();
- }
-
- var loveIterator = showLove();
-
- for (const value of loveIterator) {
- console.log(value);
- }
Output
Generator As Methods Inside An Object
Because JavaScript generator is a function too, you can add them to objects.
Let’s see some examples below.
- Object literal with a function expression
- const fruits = {
- createFruitIterator: function* (items) {
- for (let index = 0; index < items.length; index++) {
- yield items[index];
- }
- }
- }
-
- const myFruitIterator = fruits.createFruitIterator(
- ["Grapefruit",
- "Pineapple",
- "Apples",
- "Mango"]);
-
- for (const fruit of myFruitIterator) {
- console.log(fruit);
- }
- Object literal using method shorthand by pretending the method name with an asterisk (*).
- const actOfKindNess = {
- *beKindToPeopleIterator(items) {
- for (let index = 0; index < items.length; index++) {
- yield items[index];
- }
- }
- }
-
- const myKindnessActIterator = actOfKindNess.beKindToPeopleIterator(
- ["Don't discriminate",
- "Buy someone a coffee",
- "Consider kindness before you speak"]);
-
- for (const kindNess of myKindnessActIterator) {
- console.log(kindNess);
- }
The examples are almost the same as the previous examples but with different syntax.
Why I Can’t Use An Arrow Function with JavaScript Generators?
Based on my research it is because of the
yield keyword. Please see the statement below from the
MDN documentation:
“The yield keyword may not be used in an arrow function’s body unless it is further nested within it. Therefore, arrow functions cannot be used as generators.”
Here are the things I experimented with arrow functions that failed.
Arrow function and using the yield keyword. This one failed.
Arrow function and using the “yield*” keyword. This one failed too.
Arrow function and use the asterisk to act as a generator and removed the yield keyword. Again, this one failed.
Perhaps, the simple experiment is significant. However, come to think of it, I felt that using a generator in an arrow function appears vague.
Here is one of the things I observed with the arrow-function that was successful.
Before going to the sample code. Let us go back to the documentation. It says: “Unless it is further nested within it.” Let’s give it a try.
Let us see a code sample below.
-
-
-
- const mySampGenerator = () => ({
- *beKindToPeopleIterator(items) {
- for (let index = 0; index < items.length; index++) {
- yield items[index];
- }
- }
- });
-
-
-
- let mySampIterator = mySampGenerator()
- .beKindToPeopleIterator(
- ["Don't discriminate",
- "Buy someone a coffee",
- "Consider kindness before you speak"]);
-
- console.log(mySampIterator);
Output
Difference Between Generators and Iterators
At last, this is our last section of this article. Based on research and observation we can conclude that an iterator traverses a collection one at a time while a generator generates a sequence, one item at a time.
Summary
In this post, we have tackled the concepts of JavaScript generators. We have started by looking at what a JavaScript generator is, go in-depth with its instance methods.
Moreover, we have seen why we can’t use an arrow function directly, and some tips concerning generator function inside an object. Lastly, we have seen the difference between an iterator and a generator.
I hope you have enjoyed this article, as I have enjoyed writing it. Stay tuned for more. This article was originally written and posted
here. Many thanks, until next time, happy programming!