1. The iterator

Iterator is an interface mechanism introduced in ES6. Its main purpose is to provide a uniform access mechanism for all data structures with the Iterator interface deployed, that is, to perform traversal operations in a certain order. ES6 also provides a standard for traversal commands for Iterator operations, the for of loop

1.1 Default Itearator Interface

A data structure is considered “iterable” as long as it has the Symbol. Iterator property.

Iterator the Symbol. Iterator property is itself a function. Executing this function returns an iterator. When the of loop iterates over some data structure, it automatically looks for the Iterator interface, that is, Iterator returned by the Symbol. Iterator function.

Iterator calls Iterator’s next method each time it traverses, pointing in sequence to each member of the data organization. The return value of the next method returns information about the current member of the data structure, which is an object containing both value and done properties, where value is the value of the current member and done is a Boolean value indicating whether the traversal is complete.

Arrays, strings, and the new Map and Set structures in ES6 have Iterator interfaces by default. Objects do not

let arr = ['a'.'b'.'c'];
let iter = arr[symbol.iterator]():
for(const item of arr){
	console.log(item)//'a' 'b' 'c'
}


// Or (note that next traversal and rorOR traversal do not coexist)
iter.next() // {value: 'a', done: false }
iter.next () // {value: 'b', done: false }
iter.next() // {value: 'c', done: false }
iter.next() // {value: undefined, done: false }

Copy the code

Pay attention to

The above two ways of iterating through the current data structure by for of and calling the current data structure by next share the traversal state of the current data structure, if both exist. {valuet undefined, done: true}

1.2 Customizing icerators

1: Adds an Iterator interface to the instance object in class mode

class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false.value: value};
    }
    return {done: true.value: undefined}; }}function range(start, stop) {
  return new RangeIterator(start, stop);
}

for (var value of range(0.3)) {
  console.log(value); / / 0, 1, 2
}
Copy the code

Resolution:

Range (0,3) returns an instance of the class constructor, and when we loop through it, look for its [symbol.iterator] property (that is, iterator). Because the methods defined by class are added to the prototype chain of the current constructor, the instance object has an Iterator attribute.

Because this method returns a reference to this, the current instance object. So when you call Iterator’s next method, you call the next method defined in CLasa.

This class implements the Iterator interface, but there is a slight problem that each instance of it can only be iterated once

Optimization: The same instance can be selected more than once:

class RangeIterator{
	constructor(start, stop){
		this.start = start;
		this.stop= stop;
	} 
	[Symbol.iterator](){
		let start = this.start 
		let stop = this.stop
		return {
			next(){
				var value = start
				if(value<stop){
					start++
					return {done:false.value:value}
				}
				return {done:true.value:undefined}
			}
		}
	}

}

const range =  new RangeIterator(0.3);


for (var value of range) {
  console.log(value); / / 0, 1, 2
}
for (var value of range) {
  console.log(value); / / 0, 1, 2
}



Copy the code

2: Adds an Iterator interface to object

let obj = {
	// The key used to traverse the object
  data: [ 'hello'.'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        }
        return { value: undefined.done: true}; }}; }};Copy the code

While the above methods are somewhat cumbersome and tedious to define the Iterator interface, ES6’s proposed generator method is the simplest way to implement the Iterator interface

2. Generator functions

2.1: Basic mode

In general, once a function is executed, it will run to its end without any other code interrupting it. Generators provide a seemingly synchronous way of controlling asynchronous processes out of this pattern

let x = 1
function *foo(){
	x++
	yield / / pause
	console.log('x',x)
}


function bar(){
	x++
} 

const it = foo()
/ / start
it.next () 
x; / / 2
bar() 
x;/ / 3
it.next() //x,3

Copy the code

Code parsing:

1: The call to foo returns an iterator it to control the generator before execution begins

Next () starts the generator *foo and executes the first line x++. Generator execution is paused at yield, at which point next execution ends

3: Prints an x value of 1+1=2, then executes bar, 2+1=3.

Next resumes execution from yield pause, executes console.log, and prints the value of x 3

2.2 Message Passing

function *foo(x){
	const y = x* (yield)
	 return y
}


const it = foo(6)
// Start running
constRes1 = it.next ()res1: //{value:undefined,done:false}
const res2 = it.next(7)
res2: //{value:42,done:false}
Copy the code

Code parsing:

1: Pass 6 as the value of x as an argument to generator foo

2:foo(6) returns a selector iterator to the variable it

3:it.next() is paused at the first line of code in foo, yield. Notice that the first line of code hasn’t been executed yet, and y hasn’t been assigned yet. It’s just stuck at the yield of the expression to the right of the -equals sign

4: Next execute it.next(7), at which point 7 will be assigned to the currently paused Yeild keyword. Then execute x*yeild which is 6*7=42 and assign to y. If retun is next encountered, the return value will be the value of the current next return value

Question: What exactly is yield? What is the decision on the return value of next? Why does the number of next calls not match the number of yield executions?

Let’s move on to the next example:

function *foo(){
 const x = yield 2
 z++
 const y = yield (x*z)
  console.log(x,y,z)
 
} 

var z=1
var it1 = foo() 
var it2 = foo()
var val1 = it1.next().value //2---yiela2
var va12 = it2.next().value //2----yield2

val1 = it1.next(val2*10).value //40----x:20,z:2 
va12 = it2.next(val1*5).value / / 600 - x - 200, 2:3
it1.next(va12 / 2) //y:300 20 300 3
it2.next(val1 / 4) //y:10 200 10 3
Copy the code

Code parsing:

1: Create two iterators it1 and it2, both from generator foo

2: IT1 and IT2 both execute the yield expression for the first next stuck on the first line, respectively, and return the value following the first yield, that is, 2

3: IT1 calls next again and passes val2 *10, or 2*10=20, as the yield value of the whole expression, that is, x=20. And then z++ is z=2, where it1 is stuck in the yield expression on the third line. Currently, the value returned by next is the yield value, that is, x*z is 20*2=40. And assign it to val1

4: IT2 also calls next again and passes in val1*5, which is 40*5=200, as the yield value of the whole expression, which is x=200. If z++ is z-3(z is a global variable), it2 is also stuck in the yield expression on line 3, and next returns the yield value (x*2 = 200*3=600), and assigns the value to val2

5: IT1 calls next a third time, passing in val2/2 (600/2 =300) as the yield value of the whole expression, y=300. Finally print x=20,y=300,z=3

6: IT2 calls next for the third time, passing val1/4 (40/4 =10) as the yield value of the whole expression, y=10. Finally print X-200. Y-10, Z-3

Conclusion:

  • Each time the iterator calls the next method, execution must be paused at yield or stopped at return. At this point, the value in the return object after the current next method call is equal to the yield value or return value (if there is a return, next returns true and value returns). The value of the current next method is undefined if yield or return is not followed by a value
  • The yield expression whole itself (such as the yield 2 whole) also represents a value. Its value is always determined by the passing parameter when the next next method is called. What does “Next method” mean? For example, the first next() always starts the generator and runs to the first yield. However, passing the first next parameter does not assign the entire yield expression, because it is always stuck in the yieId expression, rather than “complete” the Yleld expression, and can only “complete” the first yield expression until the second next method is executed. Similarly, the third next method “completes” the second yield expression…. So the next call is generally called one more time than the yield expression completes
  • The same generator function can create multiple iterators whose internal variables are independent of each other, but can share global variables.

2.3 Other Details

  • Because the first next execution does not “complete” the first yiela expression, it is stuck in the first yield expression. The yield expression as a whole is determined by the arguments on the next call, so any arguments passed in the first call to Next are discarded
  • When next is called, if return is encountered or the last yield has been performed, the value returned by next is return (undefined if there is no return) and done is true. And no matter how many times you call next after that, it returns zero{value:undefined,done:true}.Even if subsequent code for return still has yield, it is ignored
  • If you don’t use next to consume the generator’s value, you passfor ofTo trent (essentially calling next).If a return value is encountered, only the yield value is iterated

2.4 throw

  • throughit.throw()An error thrown outside the generator that will be shut down if no error catching is done inside the generator (no try/catch is defined).
  • If there is error catching inside the generator, it will not affect subsequent traversals. But it skips the yield expression currently paused by the throw. Or once the throw method is caught, it executes the next yield expression. That is, the next method is automatically executed once, so that the next time next() is called manually, it stops at the yield expression next to the currently paused yield expression. Can’t get the return value of the yield expression skipped by the throw catch error

2.5 Yield * expression

ES6 provides yield* expressions as a workaround for executing one Generator function within another.

function* foo() {
  yield 'a';
  yield 'b';
}
function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

/ / is equivalent to


function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

Copy the code
function* concat(iter1, iter2) {
  yield* iter1;
  yield* iter2;
}

/ / is equivalent to

function* concat(iter1, iter2) {
  for (var value of iter1) {
    yield value;
  }
  for (var value of iter2) {
    yieldvalue; }}Copy the code

Conclusion:

  • If the yield expression is followed by an traverser object, I need to add an asterisk after the yield expression to indicate that I want to return the internal value of the traverser object. This is called yieldExpression. If you don’t add, which means I’m simply returning the traverser object itself
  • Generator functions following yield* (in the absence of a return statement) are equivalent to deploying a for… Of circulation.
  • Any data structure that has an Iterator interface can be iterated by yield*.

2.6 Asynchronous iterative generator

Control of asynchronous processes is another feature and application scenario of generators. In ES6, the optimal solution for asynchronous flow control comes from a combination of Promise and generator:

// The asynchronous request method
function foo(x,y){
	// Set request as an asynchronous request third-party library API, similar to the axio// Request call that returns a promise object
	return request('http://xxx.xxx'+x+y)
}


/ / generator
function *main(){
	try{
		const data - yield foo("11,"31') console,log(data) }catch (e){ console.log(e) } } const it = main() const p = it.next().value p.then(data=>{ it.next(data) },(e)=>{ it.throw(e) })Copy the code

Code parsing:

  • After the iterator it is started, the first call to next returns the value of the value after the first yield expression, which is foo’s return value, a promise object for an asynchronous request.
  • P.teng is used to wait for the promise resolution to complete, and if the request returns data, next is called again and the returned data is passed in as an argument as the overall value of the first yield expression, and assigned to data

Emphasis:

The most natural way to get the most out of a Promise and a generator is to yield a Promise and then control the execution of the generator’s iterators through that Promise