© Russ Ferguson and Keith Cirkel 2017
Russ Ferguson and Keith CirkelJavaScript Recipes10.1007/978-1-4302-6107-0_13

13. Working with Generators

Russ Ferguson and Keith Cirkel2
(1)
Ocean, New Jersey, USA
(2)
London, UK
 

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.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.211.166