How Do You Create a Generator?
Problem
You want to know what a generator is and how you make one.
Solution
Generators are functions that do not immediately execute when called on. They return an iterator object that lets you call and pass parameters at another time. You can continue to call this function until the first yield expression is executed.
The Code
function * helloGenerator(){
yield 'HELLOFRIEND';
}
var sayHello- = helloGenerator();
console.log(sayHello) //returns generator
//console.log(sayHello.next()) //returns Object
console.log(sayHello.next().value) //yield returns HELLOFRIEND
console.log(sayHello.next().value) //returns undefined
Listing 13-1.
Creating a Generator
How It Works
Generators are functions that do not execute immediately. When executing a function, instead of returning a value, an iterator object is returned. Iterators execute the function by using the next method. What is returned is an object that has two values—done and
value
. The first done will have a value of true if the iterator has reached the end and there are no more results to return. It will be false if it can produce more values. The second value called value returns any value from the iterator until undefined is returned.
How Exactly Does Yield Work with a Generator?
Problem
You want to know how the yield keyword works inside a generator?
Solution
Yield will pause a generator or resume a generator function.
The Code
function fun1(){
console.log('function 1');
}
function fun2(){
console.log('function 2');
}
function *runFun(){
yield;
yield fun1();
yield fun2();
}
var iterator = runFun();
console.log(iterator.next()); //pauses function Object {value: undefined, done: false}
console.log(iterator.next()); //returns 'function1' Object {value: undefined, done: false}
console.log(iterator.next()); //returns 'function 2' Object {value: undefined, done: false}
console.log(iterator.next()); //done = true Object {value: undefined, done: true}
Listing 13-2.
Working with Iterators
How It Works
The
yield keyword
is used to pause a function as it is running. This is very similar to the return keyword. One of the main differences is with return, the function is executed and the final value is returned to the caller. Here, the function can be executed again. When the function is paused using yield it cannot be restarted on its own. In order to restart the function, the iterator method next() must be called. In every instance, an object is returned with two properties. The done property will have a value of false, letting you know that you can run the next() method and continue using that function. The value property will return whatever value is being returned from the yield keyword.
How Do You Set Up a Generator Function?
Problem
How can you create a generator object?
Solution
Generator functions are defined using the
function keyword
with an * (asterisk) or using a GeneratorFunction constructor.
The Code
function *myGen(){
yield 'generator function'
}
var iterator = myGen();
console.log(iterator.next()); //Object {value: "generator function", done: false}
console.log(iterator.next()); //Object {value: undefined, done: true}
var GeneratorFunction = Object.getPrototypeOf(function*(){}).constructor
var myGenFunction = new GeneratorFunction('value', 'yield value');
var myGenIterator = myGenFunction();
console.log(myGenIterator.next()); //Object {value: undefined, done: false}
console.log(myGenIterator.next()); //Object {value: undefined, done: true}
Listing 13-3.
Different Ways of Creating a Generator Function
How It Works
There are two ways to create a generator function. Using the function keyword with the * (asterisk) is the best way to create one. The other way is to use the Object.getPrototypeOf method. This will return the constructor of the GeneratorFunction. This second way is less efficient and a little harder to understand.
When using the GeneratorFunction constructor, you can pass parameters; however, these parameters must be a list of comma-separated strings. The last parameter defines what the function does.
How Do You Create a Function that Maintains State ?
Problem
You want to create an iterative function that maintains state.
Solution
When a generator reaches the yield keyword, it pauses and returns the current value. The next time the next() method is called, the generator will continue to run, thereby creating a new value.
The Code
function *numCount(){
var count = 0;
while(count < 5)
yield count++;
}
var irt = numCount();
console.log(irt.next()); //Object {value: 0, done: false}
console.log(irt.next()); //Object {value: 1, done: false}
console.log(irt.next()); //Object {value: 2, done: false}
console.log(irt.next()); //Object {value: 3, done: false}
console.log(irt.next()); //Object {value: 4, done: false}
console.log(irt.next()); //Object {value: undefined, done: true}
Listing 13-4.
Creating a Function that Maintains State
How It Works
Because the yield keyword pauses the generator, the current values of the variables or state of the function is then frozen. The current value is then returned to the caller and can be updated when the next() method is called. This will run the generator and update the value until another yield keyword is reached, which would pause the generator again. Other options would be for the generator to throw an exception. You can reach the end of the function where the IteratorResult object is returned with the value property being undefined. The last option would be if a return statement is reached. This would end the generator and also return an IteratorResult, with the value property having the value of what is being returned and the done property having the value of true.
How Do You Send Values to a Generator Using the next( ) Method ?
Problem
You want to pass properties to a generator function.
Solution
Use the next() method that is part of the iterator object to pass properties to the generator.
The Code
function *returnMSG(){
var value = yield value
return value;
}
var it = returnMSG();
console.log(it.next()); //Object {value: undefined, done: false}
console.log(it.next('things')); //Object {value: "things", done: true}
Listing 13-5.
Passing a Parameter Using the next() Method
How It Works
The next() method can let you pass properties to the generator function. The first time you run the next() method, yield does not have a value. The second time next() is called, you can push a parameter to the generator.
Can You Use Throw Inside a Generator?
Problem
You want to know if you can use a try...catch block inside a generator.
Solution
Generators can let you run a throw function, either inside the generator or outside of it.
The Code
function *insideThrow(){
while(true){
try{
yield 'inside try block';
}catch(e){
console.log('inside catch')
}
}
}
var it = insideThrow();
console.log(it.next()); //Object {value: "inside try block", done: false}
console.log(it.throw(new Error('this is an error'))) //catch runs and returns last yield value Object {value: "inside try block", done: false}
function *outsideThrow(){
var value = yield value;
return value;
}
var it2 = outsideThrow();
console.log(it2.next()); //Object {value: undefined, done: false}
try{
console.log(it2.next('outside try block')); //Object {value: "outside try block", done: true}
console.log(it2.throw(new Error('this is an error'))); //catch runs
}catch(e){
console.log('outside catch')
}
Listing 13-6.
Using Throw…Catch Inside and Outside a Generator
How It Works
The first example shows that you can use try...catch blocks inside a generator and use them as expected.
Properties can also be evaluated at call time. This would give you the ability to assign a function as the default property and return the result.
Properties can also be chained together. In the last example, the value of the second property is a combination of the first property and the second property’s default value. The value of the third property follows the same pattern. When returned as an array, the values are in sequence.
Can You Create a Custom Iterator Inside an Object?
Problem
You want to create a custom iterator for an object.
Solution
Custom iterators can be created by providing a next method inside the object while keeping track of its current position.
The Code
var countdown = {
max: 3,
[Symbol.iterator]() {
return this;
},
next() {
if(this.max == undefined){
this.max = max;
}else if(this.max > -1){
return {value: this.max --};
}else{
return {done: true};
}
}
};
for (let i of countdown) {
console.log(i);
}
Listing 13-7.
Creating an Object with a Custom Iterator
How It Works
In order to make an object an iterator, it needs to know how to access items one at a time. This is done by having a next method in the object. ES6 provides some shorthand in defining properties and methods of an object. Here we can add Symbol.iterator as a property using square brackets. After that, the next method has been defined without using the word function
. This is also valid JavaScript as of ES6. This method will check the current value, change it, and return an object with the property’s value and done. Now that this object has an iterator, you can use it within a for..of loop. It is important to note as of this writing that Symbol.iterator does not have support in Internet Explorer or Safari.
How Do You Use a Custom Iterator to Make a Fibonacci Sequence of Numbers ?
Problem
You want to create a Fibonacci sequence using iterators.
Solution
Very similar to Listing 13-7, the object keeps track of the values and returns the result.
The Code
var fibObj = {
one: 0,
two: 1,
temp: 0,
[Symbol.iterator](){
return this;
},
next(){
this.temp = this.two;
this.two = this.temp + this.one;
this.one = this.temp;
return {value: this.two}
}
}
for(var I = 0 ; I < 1000; I++){
consolel.log(fibObj.next().value) //1,2,3,5,8.....
}
Listing 13-8.
Using a Custom Iterator to Make a Fibonacci Sequence
How It Works
Recall that a Fibonacci sequence
is the sum of the previous two numbers. Knowing that, our object needs to take two numbers, add them, return the value, and remember the new sum.
In our object we have three properties—one, two
, and temp. Similar to the previous exercise, we add both the Symbol.iterator and next methods. Inside our next method, we use the properties of the object to keep track of the values. This exercise assigns the value of property two to temp. Then it takes temp and one adds them together and assigns the new value. Then it reassigns the value of one. Finally, it returns an object with the property value and the next number in our Fibonacci sequence.