Book Image

Advanced JavaScript

By : Zachary Shute
Book Image

Advanced JavaScript

By: Zachary Shute

Overview of this book

If you are looking for a programming language to develop flexible and efficient applications, JavaScript is an obvious choice. Advanced JavaScript is a hands-on guide that takes you through JavaScript and its many features, one step at a time. You'll begin by learning how to use the new JavaScript syntax in ES6, and then work through the many other features that modern JavaScript has to offer. As you progress through the chapters, you’ll use asynchronous programming with callbacks and promises, handle browser events, and perform Document Object Model (DOM) manipulation. You'll also explore various methods of testing JavaScript projects. In the concluding chapters, you'll discover functional programming and learn to use it to build your apps. With this book as your guide, you'll also be able to develop APIs using Node.js and Express, create front-ends using React/Redux, and build mobile apps using React/Expo. By the end of Advanced JavaScript, you will have explored the features and benefits of JavaScript to build small applications.
Table of Contents (9 chapters)

Iterators and Generators


In their simplest forms, iterators and generators are two ways to process a collection of data incrementally. They gain efficiency over loops by keeping track of the state of the collection instead of all of the items in the collection.

Iterators

An iterator is a way to traverse through data in a collection. To iterate over a data structure means to step through each of its elements in order. For example, the for/in loop is a method that's used to iterate over the keys in a JavaScript object. An object is an iterator when it knows how to access its items from a collection one at a time, while tracking position and finality. An iterator can be used to traverse custom complicated data structure or for traversing chunks of large data that may not be practical to load all at once.

To create an iterator, we must define a function that takes a collection in as the parameter and returns an object. The return object must have a function property called next. When next is called, the iterator steps to the next value in the collection and returns an object with the value and the done status of the iteration. An example iterator is shown in the following code:

function createIterator( array ){
  let currentIndex = 0;
  return {
    next(){
      return currentIndex < array.length ?
        { value: array[ currentIndex++ ], done: false} :
        { done: true };
    }
  };
}

Snippet 1.81: Iterator declaration

This iterator takes in an array and returns an object with the single function property next. Internally, the iterator keeps track of the array and the index we are currently looking at. To use the iterator, we simply call the next function. Calling next will cause the iterator to return an object and increment the internal index by one. The object returned by the iterator must have, at a minimum, the properties value and done. Value will contain the value at the index we are currently viewing. Done will contain a Boolean. If the Boolean equals true, then we have finished the traversion on the input collection. If it is falsy, then we can keep calling the next function:

// Using an iterator 
let it = createIterator( [ 'Hello', 'World' ] );
console.log( it.next() );
// Expected output: { value: 'Hello', done: false }
console.log( it.next() );
// Expected output: { value: 'World' , done: false }
console.log( it.next() );
// Expected output: { value: undefined, done: true }

Snippet 1.82: Iterator use

Note

When an iterator's finality property is truthy, it should not return any new data. To demonstrate the use of iterator.next(), you can provide the example shown in the preceding snippet.

In summary, iterators provide us with a way to traverse potentially complex collections of data. An iterator tracks its current state and each time the next function is called, it provides an object with a value and a finality Boolean. When the iterator reaches the end of the collection, calls to iterator.next() will return a truthy finality parameter and no new values will be received.

Generators

A generator provides an iterative way to build a collection of data. A generator can return values one at a time while pausing execution until the next value is requested. A generator keeps track of the internal state and each time it is requested, it returns a new number in the sequence.

To create a generator, we must define a function with an asterisk in front of the function name and the yield keyword in the body. For example, to create a generator called testGenerator, we would initialize it as follows:

function *testGen( data ) { yield 0; }.

The asterisk designates that this is a generator function. The yield keyword designates a break in the normal function flow until the generator function is called again. An example of a generator is shown in the following snippet:

function *gen() {
 let i = 0;
 while (true){
   yield i++;
 }
}

Snippet 1.83: Generator creation

This generator function that we created in the preceding snippet, called gen, has an internal state variable called i. When the generator is created, it is automatically initialized with an internal next function. When the next function is called for the first time, the execution starts, the loop begins, and when the yield keyword is reached, the execution of the function is stopped until the next function is called again. When the next function is called, the program returns an object containing a value and done.

Exercise 15: Creating a Generator

To create a generator function that generates the values of the sequence of 2n to show how generators can build a set of sequential data, perform the following steps:

  1. Create a generator called gen.

    Place an asterisk before the identifier name.

  2. Inside the generator body, do the following:

    Create a variable called i and set the initial value to 1. Then, create an infinite while loop.

    In the body of the while loop, yield i and set i to i * 2.

  3. Initialize gen and save it into a variable called generator

  4. Call your generator several times and log the output to see the values change.

Code

index.js:
function *gen() {
 let i = 1;
 while (true){
   yield i;
   i = i * 2;
 }
}
const generator = gen();
console.log( generator.next(), generator.next(), generator.next() );

Snippet 1.84: Simple generator

Outcome

Figure 1.17: Calling the generator output

You have successfully created a generator function.

Similar to iterators, the done value contains the completion status of the generator. If the done value is set to true, then the generator has finished execution and will no longer return new values. The value parameter contains the result of the expression contained on the line with the yield keyword. In this case, it will return the current value of i, before the increment. This is shown in the following code:

let sequence = gen();
console.log(sequence.next());
//Expected output: { value: 0, done: false }
console.log(sequence.next());
//Expected output: { value: 1, done: false }
console.log(sequence.next());
//Expected output: { value: 2, done: false }

Snippet 1.85: Generator use

Generators pause execution when they reach the yield keyword. This means that loops will pause execution. Another powerful tool of a generator is the ability to pass in data via the next function and yield keyword. When a value is passed into the next function, the return value of the yield expression will be set to the value that's passed into next. An example of this is shown in the following code:

function *gen() {
 let i = 0;
 while (true){
   let inData = yield i++;
   console.log( inData );
 }
}

let sequence = gen();
sequence.next()
sequence.next( 'test1' )
sequence.next()
sequence.next( 'test2' )

// Expected output:
// 'test1'
// undefined
// 'test2'

Snippet 1.86 Yield keyword

In summary, generators are an iterative way of building a collection of data. They return values one at a time while tracking internal state. When the yield keyword is reached, internal execution is stopped and a value is returned. When the next function is called, execution resumes until a yield is reached. Data can be passed into a generator through the next function. Data that's passed in is returned through the yield expression. When a generator emits a value object with the done parameter set to true, calls to generator.next() should not yield any new values.

In the final topic, we introduced iterators and generators. Iterators traverse through data in a collection of data and return the value requested at each step. Once they have reached the end of the collection, a done flag is set to true and no new items will be iterated over. Generators are a way to generate a collection of data. At each step, the generator produces a new value based on its internal state. Iterators and generators both track their internal state as they progress through their life cycle.

Activity 1: Implementing Generators

You have been tasked with building a simple app that generates numbers in the Fibonacci sequence upon request. The app generates the next number in the sequence for each request and resets the sequence it is given as input. Use a generator to generate the Fibonacci sequence. If a value is passed into the generator, reset the sequence.

To build a complex iterative dataset using a generator, perform the following steps:

  1. Look up the Fibonacci sequence.

  2. Create a generator that provides the values in the Fibonacci sequence.

  3. If the generator's yield statement returns a value, reset the sequence

Outcome

Figure 1.18: Implementing generators output

You have successfully created a generator that can be used to build an iterative dataset based on the Fibonacci sequence.

Note

The solution for this activity can be found on page 280.