preface

What are iterators and generators? There are many concepts that people often don’t understand and ignore.

Iterators are essentially a function similar to the Promise+ specification, which is a compound iterator specification. The iterator function returns an object containing the next method, which returns an object containing the value done attribute.

A generator is function * (){} and is paused with yield.

1. The iteration

1.1 What is iteration

An iteration is a sequence of programs that are executed with termination conditions. Remember three characteristics: order, loop traversal, and termination conditions.

  • Ordered: Iterations take place over an ordered set. “Ordered” means that all items in the set can be traversed in a defined order, where the beginning and end items are clearly defined. (A set of numbers is an ordered set)
  • Loop iteration: Each loop is completed before the next iteration begins.
  • Termination condition: the number of times the loop is iterated. (A cycle without termination conditions is an endless cycle, so it’s not over.)
const colors = ["red"."green"."blue"];
colors.forEach(item= >console.log(item));
//"red"
//"green"
//"blue"
Copy the code

1.2 Defects of array iteration

  • You need to know how to use data structures before you iterate.
  • Does not apply to implicitly sequential data structures that are not divisor groups.

1.3 Array iteration in ES5

  • forEach()
  • map()
  • some()
  • every()
  • filter()

2. The iterator

2.1 Iterators and iterables

An iterator is an object designed specifically for iteration, with a specific interface. That is, disposable objects created on demand.

Iterables are structures that implement a formal Iterbale interface and can be consumed by the Iterator Iterator. In human terms, an object from which data can be extracted following certain rules.

These rules are:

  • The main object/class should store some data.
  • The main object/class must have globalSymbol, and this property must use a specialSymbol.iteratorAs the key.
  • Symbol.iteratorMethod must have a new iterator object — the “default iterator”.
  • The iterator object must have a next() method
  • Each successful call to next() must return an iteration result object containing the next value returned by the iterator
  • The current position of the iterator cannot be known without calling next()

Each iterator is associated with an iterable, and the iterator exposes the API that iterates over its associated iterable.

2.2 Iterable protocol

Iterable protocols have two capabilities:

  • Support iterative self-awareness. Used to address the defect of needing to know the data structure type in advance before iteration.
  • The ability to create objects that implement iterable interfaces. Used to resolve implicit sequential data structures where iteration does not apply outside of a divisor group.

The iterator behavior is obtained by exposing the “default iterator” property on an object or its prototype chain. This property uses the special symbol. iterator as a key and calls the iterator factory function to return a new iterator.

Type of built-in iterable interface

  • String
  • Array
  • Map
  • Set
  • argument
  • DOM collection objects such as NodeList

Receives the syntax of an iterable

  • For – loops
  • An array of deconstruction
  • Extended operator
  • Yield * operator, used in generators
  • Array.from()
  • Create collection new Map()
  • Create mapping new Set()
  • Promise.all() receives an iterable made up of promises
  • Promise.race() receives an iterable made up of promises

These syntactic constructs create an iterator by calling the factory function of the provided iterable behind the scenes.

Custom iterable

If the parent of the object’s prototype chain also implements the Iterable interface, then the object implements the Iterable interface.

class SonArray extends Array{}
const sonArray = new SonArray("zhangsan"."lisi"."waner");
for(let item in sonArray){
  console.log(item);
}
// "zhangsan"
// "lisi"
// "wenger"
Copy the code

2.3 Iterator protocol

We know that an iterator is a disposable object used to iterate over the iterable associated with it.

In a nutshell: only objects that implement the next() method can be iterators.

Next () returns two values: done and value. Done indicates that the next call is successful and the next call to next() can be made. Value represents the value containing the next call to next(). (Done is false, value is the corresponding value; Done: true, value: undefined)

For example:

// Iterable
const colors = ["red"."green"."blue"];
// Iterator factory function to get iterators
let iter = colors[Symbol.iterator]();
// Perform iteration
console.log(iter.next());//{done:false,value:"red"};
console.log(iter.next());//{done:false,value:"green"};
console.log(iter.next());//{done:false,value:"blue"};
Copy the code

We see that we iterate through the array sequentially by creating an iterator and calling the next() method until all values are iterated (that is, we stop iterating when done is true).

Iterator features:

  • Each iterator represents a one-time ordered traversal of the iterable.
  • Iterators maintain a reference to the iterable, so iterators prevent the garbage collector from collecting the iterable.

The concept of an iterator is somewhat vague, sometimes referring to an iteration, sometimes to an interface, and sometimes to an iterator type. The iterator looks like this:

class Colors{
  / / the iterator
  [Symbol.iterator] () {
    return {
    	/ / next () function
      next () {
      	/ / return IteratorReault
        return { done: false.value: 'red'}; }}}}// Create an instance object
const colors = new Colors();
// Prints an object that implements the iterator interface
console.log(colors[Symbol.iterator]()); // { next: colors () {} }
//
console.log(color[Symbol.iterator]().next()); // {done: false, value: "red"}

Copy the code

2.4 Custom iterators

Any object that implements the Iterator interface can be used as an Iterator. In order to iterate over an iterable multiple times, you need to create pairs of iterators on the Iterator, each corresponding to a new counter, and put the counter variable into a closure that returns the Iterator.

Such as:

class Counter {
  constructor (limit) {
    this.limit = limit;
  }

  [Symbol.iterator] () {
    let count = 1,
      limit = this.limit;
    retrun  {
      next () {
        if (count <= limit) {
          return { done: false.value: count++ };
        } else {
          return { done: true.value: undefined };
        }
      }
    }
  }
}

let counter = new Counter(3);
for(let i of counter){
  console.log(i);
}
/ / 1
/ / 2
/ / 3
Copy the code

Thus, each iterator created in this manner implements an iterable interface.

2.5 Terminate iterators early

The iterator’s optional return() can be used to specify that the iterator prematurely terminates the execution of the iteration.

  • The for-of loop exits prematurely with break\continue\return\throw, etc
  • Deconstructing assignment does not consume all values

Such as:

class Counter {
  constructor (limit) {
    this.limit = limit;
  }

  [Symbol.iterator] () {
    let count = 1,
      limit = this.limit;
    return {
      next () {
        if (count <= limit) {
          return { done: false.value: count++ };
        } else {
          return { done: true.value: undefined}; }},/ / to join the return
      return () {
      	console.log('Operation before iterator closes');
        return { done: true}; }}}}let counter = new Counter (5);

for (let i of counter) {
  if (i > 2) break;
  console.log(i);
}
/ / 1
/ / 2
// Perform the operation before the iterator closes

Copy the code

Note:

  • There is no setreturn()If the iterator is closed or cannot be closed, the iteration exits and continues where it left off last time
  • Set thereturn()Is not forced into a closed state, butreturn()Is called

2. The generator

Now that we’ve sorted out the basic concepts and features of iterators, let’s move on to generators.

2.1 What is a generator

Generator functions are asynchronous programming solutions provided by ES6. The syntax is different from traditional functions. A generator function is a state machine that encapsulates multiple internal states, suspends execution with yield, and proceeds to the next iteration with next().

In short, generators are functions that control iterators and can be paused and resumed at any time.

Generators can also be thought of as producers of a value, which can be called once through next() of the iterator interface.

How to define a generator is as simple as prefixing the function definition with * :

// function declarative generator
function *fun(){}
// Function expression generator
let fun = function* (){}
// a literal generator
let fun = {
  * foo(){}}/ / etc.
Copy the code

2.2 Characteristics of generators

  • The asterisk * that identifies a generator function is not affected by space on either side
  • Arrow functions cannot be used to define generator functions (note that arrow functions do not have arguments)
  • Calling a generator function produces a generator object
  • The generator object’s initial state issuspended(suspended) state
  • Built-in generator objectIteratorInterface, withnext()methods
  • Inside the function body, yield expressions are used to define different internal states
  • Called when a generator function is executed piecewisenext()Method function internal logic starts executing
  • Generator functions with an empty body do not stop in the middle and are called oncenext()To reachdone:truestate
  • The generator object is only called the first timenext()Method before it is executed
  • Generator objects implement an iterable interface, and the default iterator is self-referential

Such as:

let generatorFn = function* () {
  console.log('execution');
  return 'generator'
};

const g  = generatorFn();
console.log(g); // generatiorFn {<suspended>}
console.log(g.next);ƒ next() {[native code]}
console.log(g.next());
/ / execution
// {value: "generator", done: true}

console.log(generatorFn);
/* ƒ* () {console.log(' run '); return 'generator' }*/
console.log(generatorFn()[Symbol.iterator]); ƒ [Symbol. Iterator]() {[native code]}
console.log(generatorFn()); // generatorFn {<suspended>}
console.log(generatorFn()[Symbol.iterator]()); // generatorFn {<suspended>}

Copy the code

Let’s take a look at how the Generator function performs piecewise execution.

function *Gen(){
  yield "wenbo";
  yield "bowen";
  yield "zhaoshun";
  return "ending";
}

const gen = Gen();
gen.next();//{done:false,value:"wenbo"}
gen.next();//{done:false,value:"bowen"}
gen.next();//{done:false,value:"zhaoshun"}
gen.next();//{done:true,value:undefined}
Copy the code

We see that the first three calls to next() will yield, at which point the function will pause and return value and done, respectively. If done is not true, the iteration has not stopped.

A return is encountered when next() is executed for the fourth time (if there is no return, until the end of the function). The value property of the object returned by the next() method, followed by the value of the expression following the return statement, in which case done is true, indicating the completion of the traversal.

Yield interrupts execution

The yield keyword causes the generator to stop and start execution. When a generator function is first executed, execution is paused at yield, and the state of the function scope is preserved. When the next() method is called, the generator function can only resume execution by calling the next() method on the generator object.

Generator objects as iterables We can call next() explicitly on generator objects differently, and we can treat iterators as iterables.

function *Gen(){
  yield "wenbo";
  yield "bowen";
  yield "zhaoshun";
}
for(const item of Gen()){
  console.log(item);
}
// "wenbo"
// "bowen"
// "zhaoshun"
Copy the code

Output yield can be used either as an intermediate return statement of a function or as an intermediate argument to a function. The value passed in from the first call to next() is not used in order to execute the generator function.

function *Gen(init){
  console.log(init);
  console.log(yield);
  console.log(yield);
}

const gen = Gen("fun");
gen.next("wenbo");//wenbo
gen.next("bowen");//bowen
gen.next("zhaoshun");//zhaoshun
Copy the code

Generating an iterable enhances the behavior of yield with the asterisk *, making it possible to iterate over one iterable, yielding one value at a time.

Because yield* is really just a serialization of an iterable into a single output of values, it makes no difference in a loop.

function *Gen(){
  for(const x of [1.2.3]) {yieldx; }}/ / equivalent to the
function *Gen(){
  yield* [1.2.3];
}
Copy the code

The Iterator interface is implemented in generator objects, and both generator functions and default iterators are called to produce iterators, so generators are suitable as default iterators. Since a Generator function is an iterator Generator function, you can assign Generator to the Symbol. Iterator property of an object so that the object has an Iterator interface.

let obj = { username: 'wenbo'.age: 19 }
obj[Symbol.iterator] = function* myTest() {
  yield 1;
  yield 2;
  yield 3;
};
for (let i of obj) {
  console.log(i) / / 1 2 3
}

Copy the code

Premature termination generator

An object that implements the Iterator interface must have a next() method and an optional return() method to prematurely terminate the Iterator. The return() and throw() methods can be used to force the generator into a closed state.

  • The return() method forces the generator into a shutdown state. The value supplied to the return() method is the value of the terminator object
  • The throw() method injects a supplied error into the generator object when paused. If the error is not handled, the generator is shut down.
function* Gen(){
  for(const x of ["red"."green"."blue"]) {yieldx; }}const gen = Gen();
console.log(gen.next());//{done:false,value:"red"}
console.log(gen.return("yellow"));//{done:false,value:"yellow"}
console.log(gen.next());//{done:true,value:undefined}
console.log(gen.next());//{done:true,value:undefined}


function *Fun(){
  for(const x of ["red"."green"."blue"]) {yieldx; }}const fun = Fun();
console.log(fun);//Fun {<suspended>}
try{
  fun.throw("yellow");
}catch(e){
  console.log(e);//yellow
}
 console.log(fun);//Fun {<closed>}
Copy the code

Application scenarios

Coroutines scenario

function  schoolTask(goSchool,endSchool){
   let goSchoolIterator=goSchool();
   let endSchoolIterator=endSchool();
   console.log(goSchoolIterator.next().value);
   console.log(endSchoolIterator.next().value);
   console.log(endSchoolIterator.next().value);
   console.log(endSchoolIterator.next().value);
   console.log(goSchoolIterator.next().value);
   console.log(endSchoolkIterator.next().value);
}
function  *goSchool(){
   yield "I went to elementary school.";
   yield "I went to middle school";
   yield "I went to college.";
}

function  *endSchool(){
   yield "I graduated from elementary school.";
   yield "I graduated from high school.";
   yield "I graduated from college";
}

schoolTask(goSchool,endSchool);
/* I went to primary school. I graduated from primary school. I went to middle school

Copy the code

Asynchronous operation synchronization

At the heart of asynchronous ajax operation synchronization is the yield keyword in the Generator function, which interrupts execution!

In essence:

When the iterator IT calls the next() method for the first time, starting the generator, Ajax will initiate the request. When the Ajax is done, it will initiate the next() method again, using the callback function, assigning the received data to the last yield expression, and the variable result will receive the request from Ajax. Finally, the requested data is formatted and output.

function* main() {
   var result = yield request("http://some.url");
   var resp = JSON.parse(result);
   console.log(resp.value);
}

function request(url) {
   makeAjaxCall(url, function(response){
       it.next(response);
   });
}

var it = main();
it.next();

Copy the code

summary

  • Iterator: An interface that can be implemented by any object, supporting the continuous retrieval of a single output of an object. Any implementation可迭代Each object of the interface has oneSymbol.iteratorProperty that references the default iterator. The default iterator is that iterator factory, and when called it produces an implementationIteratorInterface object.
  • Generator: A special function that returns a generator object when called. The generator object is implemented可迭代Interface, and therefore can be used anywhere an iterable is consumed. Generator support yieldKeyword that can suspend execution of generator functions; useyieldCan be achieved bynext()Method takes input and produces output.

Refer to the article

ES6 Iterators and Generators

[JS Little Red Book Notes] Iterators and Generators

JavaScript Advanced Programming (4th Edition)

Ruan Yifeng, Introduction to ES6 Standards

Write in the last

Thank you for reading, I will continue to share with you more excellent articles, this article reference a large number of books and articles, if there are mistakes and mistakes, hope to correct.