Iterator (traverser)

What is an iterator?

First, an iterator is an object (which is special) that iterates through an array-like data structure

Second, there are two methods in this object

  • Next () // Iterate over the members of the data structure
  • Return () // Terminates traversal

Why iterators?

Can improve development efficiency! Developers don’t need to know the structure of the data structure to get the data in a certain order. Isn’t it very high class?

So how do you generate an iterator?

To know how to generate an iterator, we need to know what is an iterable protocol

Implementing the Iteratable interface (an iterable protocol) requires a combination of two capabilities: the ability to self-identify to support iteration and the ability to create objects that implement the Iterator interface. In ES, this means that an attribute must be exposed as the “default iterator “, and that attribute must use the special symbol.iterator as the key. The default iterator property must refer to an iterator factory function, which must return a new iterator.

In plain English, when creating an iterable, the iterable must define an iterator factory function so that it can use its iterators to iterate over its own data.

Many built-in types are iterable, which means they have their own built-in iterators! , such as

  • string
  • An array of
  • mapping
  • A collection of
  • The arguments object
  • DOM collection type such as NodeList

Play with the iterator now!

let array=["abc"."hello"."hy"."world"];
let arrayIterator=array[Symbol.iterator]();
/* Call the array iterator factory function to produce an array iterator */
console.log(arrayIterator.next());//{ value: 'abc', done: false }
console.log(arrayIterator.next());//{ value: 'hello', done: false }
console.log(arrayIterator.next());//{ value: 'hy', done: false }
console.log(arrayIterator.next());//{ value: 'world', done: false }
console.log(arrayIterator.next());//{ value: undefined, done: true }
Copy the code

Isn’t it amazing? , the above code can continue to optimize!

let array=["abc"."hello"."hy"."world"];
let arrayIterator=array[Symbol.iterator]();

for(value of  array){
    console.log(value);
}
/*
abc
hello
hy
world
*/
console.log("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --")
for(value   of  arrayIterator){
    console.log(value);
}
/* The iterator of the built-in type is itself an iterable, so it can also be for.... ABC hello hy world */
Copy the code

Why is it possible to write that up here

Since there are several native language features that implement the Iterator interface, the Iterator interface can consume Iterable as long as it is implemented. Of is essentially an iterator, so it can consume arrays (iterables), not just for… Of and the following native language features implement the Iterator interface

  • An array of deconstruction
  • Extension operator
  • Array.from()
  • Create a collection
  • Create a mapping
  • Promise.all() Is an iterable composed of acceptance contracts
  • Promise.race() Is an iterable composed of acceptance contracts
  • Yield * operator, used in generators

Terminate the iterator early

Using an iterator object return () method, which can stop the iterator traverses, but should pay attention to is like an array of built-in objects, even if you execute the return () method, it is not going to close the iterator, and it will be kept before traversal of the scene, such as continue to bloom for next time, will then traverse the last place, continue to traverse.

So here’s the code

let array=[1.2.3.4.5];

let arrayIterator=array[Symbol.iterator]();
arrayIterator.return=function(){
    console.log("Stop traversal");
    return {done:true};
}

for(value  of  arrayIterator){
    if(value >2) {break;
    }
    console.log(value);
}
/* 1 2 stop traversal */
console.log(arrayIterator.next());//{ value: 4, done: false }
console.log(arrayIterator.next());//{ value: 5, done: false }
console.log(arrayIterator.next());//{ value: undefined, done: true }
Copy the code

Define a custom iterator

After looking at the built-in iterators above, do you want to implement an iterator for yourself ^^

Now let’s talk about what it takes to implement a custom iterator!

1 variable is defined in the iterator factory function

  • Index: The position to hold the iterator pointer so that the next iteration will be followed by the next iteration (using closures to save the traversal scene)
let obj={
    data: [0.1.3],
    [Symbol.iterator](){
        let index=0;// Save the position of pointer
        const self=this;
        return {
            next(){
                if(index<self.data.length)
                {
                    return {value:self.data[index++],done:false};
                }
                else
                    return {value:"undefined".done:true}
            }
        }
    }
}

let objIterator =obj[Symbol.iterator]();
console.log(objIterator.next());//{ value: 0, done: false }
console.log(objIterator.next());//{ value: 1, done: false }
console.log(objIterator.next());//{ value: 3, done: false }
console.log(objIterator.next());//{ value: 'undefined', done: true }
Copy the code

Look at the picture is not a lot clearer ^^

The generator

The Generator function is an asynchronous programming solution provided by ES6. It is either a state machine or an iterator Generator function.

Calling a Generator function returns an iterator that calls the next() function to iterate over the state in the Generator function.

Each yield keyword represents a state of the Generator function. As soon as the iterator traverses the yield keyword, the Generator function interrupts execution. When will it execute again? When the iterator calls the next() method again, the generator function starts executing again.

Let’s declare a simple Generator function.

function  * createGenerator(){
    console.log("start");
    yield  1;
    yield  2;
    yield  3;
    return "ending";
}


let Generator=createGenerator();// An iterator is generated. The generator function is called, but the code inside it is not executed. Only the next() method of the iterator can drive the generator function to execute the internal code.
console.log(Generator.next());
/* The Generator function starts executing until the first yield expression is encountered. The next method returns an object (IteratorResult) with the value of the current yield expression value 1 and the done value false, indicating that the traversal has not finished. So the "console.log("start")" in front of the "yield 1" statement is executed. * /
console.log(Generator.next());//{done:false,value:2}
console.log(Generator.next());//{done:false,value:3}
console.log(Generator.next());//{done:true,value:"ending"}
console.log(Generator.next());//{done:true,value:undefined}
console.log(Generator.next());//{done:tre,value:undefined}
Copy the code

Generator objects can be used as iterables

Without further ado, let’s get right to the code

function *generator(){
    yield   1;
    yield   2;
    yield   3;
    yield   4;
}

let generatorIterator=generator();// The generator function returns an iterator (generator object)
let generatorIteratorIterator=generatorIterator[Symbol.iterator]();
/* The iterator returned by a generator function is essentially an iterable because it has a default iterator, so we can use for... The of structure consumes the generator object */
console.log(generatorIteratorIterator)
for(item of generatorIterator){
    console.log(item);
}
/* 1, 2, 3, 4 */
// This is equivalent to the following code
console.log(generatorIterator.next().value);/ / 1
console.log(generatorIterator.next().value);/ / 2
console.log(generatorIterator.next().value);/ / 3
console.log(generatorIterator.next().value);/ / 4
Copy the code

Use yield to implement input and output

Ruan yifeng god from the copy of an example, I feel particularly good to understand

To clarify, the yield expression always returns undefined unless the next() method injects an argument to the previous yield expression. (This may be a little confusing, but I’ll do the figure above ^^ later.)

function* foo(x) {
    var y = 2 * (yield (x + 1));
    var z = yield (y / 3);
    return (x + y + z);
}

var a = foo(5);
console.log(a.next()); // Object{value:6, done:false}
console.log(a.next()); // Object{value:NaN, done:false}
console.log(a.next()); // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
Copy the code
  1. Declares a variable, a, that receives an iterator returned by generator foo.
  2. Execute the first a.ext (), and the first a.ext () will output an IteratorResult object containing both value(yield expression value) and done(traversal completed), especially !!!! Assignment to y has been interrupted!!
  3. Execute the second A.next (), because the second A.next () method does not inject arguments into the generator function, so the last yield expression returns undipay, and the result is Y =2*undefined, which is why the output value of the second A.next () is NaN, The assignment to variable Z is then interrupted.
  4. Execute the third A.next (), because the third A.next () method does not inject arguments into the generator function, so the last yield expression returns undipay, and the result is z=undefined, which is why the output of the third A.next () is NaN

Everyone to simulate it!

Produces an iterable

You can use an asterisk to enhance the behavior of yield so that it iterates over an iterable, directly to the code

function *Generator(){
    yield* [1.2.3]}for(item  of    Generator()){
    console.log(item);
}
/ / equivalent
function *Generator(){
    yield   1;
    yield   2;
    yield   3;
}

for(item  of    Generator()){
    console.log(item)
}
Copy the code

Generator as default iterator

let obj={
    data: [1.2.3],
    [Symbol.iterator](){
        let index=0;
        let self=this;
        return {
            next(){
                if(index<self.data.length){
                    return{
                        value:self.data[index++],
                        done:false}}else
                    return{
                        value:undefined.done:true
                    }
            }
        }
    }
}

let obj1= {
    data: [1.2.3],
    [Symbol.iterator]:function *Generator(){
        yield *this.data; }}for(item of obj){
    console.log(item);
}
/* 1, 2, 3 */

for(item of obj1){
    console.log(item);
}
/* 1, 2, 3 */
Copy the code

After reading the above code, it’s convenient to have generators as the default iterator!

Premature termination generator

The return method

The generator’s return() method can be used to prematurely terminate the generator. After the generator is shut down, subsequent calls to the next() method will display the done:true state, and any return values provided will not be stored or propagated.

function *generator(){
    yield  1;
    yield  2;
    yield  3;
    yield  4;
    yield  5;
    yield  6;
    yield  7;
    yield  8;
    yield  9;
}

let Iterator=generator();
console.log(Iterator.next());//{ value: 1, done: false }
console.log(Iterator.next());//{ value: 2, done: false }
console.log(Iterator.return("Generator off"));//{value: 'generator off ', done: true}
console.log(Iterator.next());//{ value: undefined, done: true }
console.log(Iterator.next());//{ value: undefined, done: true }
console.log(Iterator.next());//{ value: undefined, done: true }
console.log(Iterator.next());//{ value: undefined, done: true }
Copy the code

Throw method

The throw() method injects a supplied error into the generator object when paused. If the error is not handled, the generator closes and goes directly to the code.

function *generator(){
    yield  1;
    yield  2;
    yield  3;
    yield  4;
    yield  5;
    yield  6;
    yield  7;
    yield  8;
    yield  9;
}

let Iterator=generator();
try {
    console.log(Iterator.next());
    console.log(Iterator.next());
    console.log(Iterator.throw("Generator off"));
    console.log(Iterator.next());
    console.log(Iterator.next());
    console.log(Iterator.next());
    console.log(Iterator.next());
}catch(e){
    console.log(e);
}
/* {value: 1, done: false} {value: 2, done: false} Generator closes */------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------function *generator(){
    try{
        yield  1;
        yield  2;
        yield  3;
        yield  4;
        yield  5;
        yield  6;
        yield  7;
        yield  8;
        yield  9;
    }catch(e){
        console.log(e); }}let Iterator=generator();
try {
    console.log(Iterator.next());
    console.log(Iterator.next());
    console.log(Iterator.throw("Generator off"));
    console.log(Iterator.next());
    console.log(Iterator.next());
    console.log(Iterator.next());
    console.log(Iterator.next());
}catch(e){
    console.log(e);
}
/* {value: 1, done: false} {value: 2, done: false} {value: undefined, done: true} {value: undefined, done: true} {value: undefined, done: false} true } { value: undefined, done: true } { value: undefined, done: true } { value: undefined, done: true } */
Copy the code

That’s it for generators. Let’s talk about some of their applications

Application scenarios

Coroutines scenario

What is a coroutine?

I will not say more here, you can refer to the article of Liao Xuefeng teacher, directly on the code

function  cookTask(wash,cook){
    let washIterator=wash();
    let cookIterator=cook();
    console.log(washIterator.next().value);
    console.log(cookIterator.next().value);
    console.log(washIterator.next().value);
    console.log(cookIterator.next().value);
    console.log(washIterator.next().value);
    console.log(cookIterator.next().value);
}
function  *wash(){
    yield "I washed the cabbage.";
    yield "I washed the pork.";
    yield "I washed the chicken wings.";
}

function  *cook(){
    yield  'I've cooked the cabbage.';
    yield  'I cooked the pork.';
    yield  'I cooked the chicken wings.';
}

cookTask(wash,cook);
/* I washed the cabbage I cooked the cabbage I washed the pork I cooked the pork I washed the chicken wings I cooked the chicken wings */
Copy the code

Here we simulate the scene of cooking, where there is a robot chef. First of all, the robot will perform the function of washing dishes. When the function of washing dishes is finished, the robot will transfer the control to the function of cooking dishes.

Asynchronous operation synchronization

This is an example of Ruan Yifeng

At the heart of synchronizing asynchronous Ajax operations is the yield keyword in the Generator function, which interrupts execution!

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

Let me give you some ideas

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.

Isn’t it amazing to use Generator functions to synchronize asynchronous tasks

At the end

This is the end of the article, if there is anything wrong with the above content, I hope the big guys can point out ^^