ES6: The ins and outs of Generator
Overview
-
Generator function is an asynchronous programming solution provided by ES6. It can be understood as a state machine that encapsulates multiple internal states. -
-
Executing the Generator function will return a iterator object, which can traverse each state inside the function in turn;
-
The Generator function has two characteristics:
- There is an asterisk between function and function name;
- The yield statement is used inside the function body to define different internal states.
-
Example:
function*helloWorldGenerator(){ yield '听不见!'; yield '重来!'; return '很有精神!' } let hw = helloWorldGenerator(); hw.next(); // { value : '听不见!',done : false } hw.next(); // { value : '重来!',done : false } hw.next(); // { value : '很有精神!',done : true } hw.next(); // { value : undefined , done : true }
-
Using this feature, you can realize the asynchronous programming mechanism of the function;
yield expression
-
The running mechanism of the next method of the traverser is as follows:
- When encountering a yield statement, the execution of the following operations will be suspended, and the value of the yield expression will be returned as the value attribute value of the returned object;
- The next time the next call is called to continue execution, until the next yield statement is encountered;
- If there is no yield, run directly to the return statement;
- If there is no return statement, an object whose value is undefined is returned.
-
Yield must be placed in the Generator function before it can be used;
-
If yield is used in another expression, parentheses must be added!
console.log('hello' + yield 123); ///error console.log('hello' + (yield 123)); //ok
Parameters of the next method
-
The next method can take a parameter, which will be used as the return value of the previous statement. When next has no parameters, it returns undefined;
function* f(){ for(let i=0;ture;i++){ let reset = yield i; if(reset){ i = -1; } } } let g = f(); g.next() //{value : 0,done : false}; g.next() //{value : 1,done : false}; g.next(true) //{value : 0,done : false};
for...of method
-
The for...of loop can automatically traverse the Iterator object generated by the Generator function, eliminating the need to use the next method;
function* foo(){ yield 1; yield 2; yield 3; yield 4; yield 5; } for (let v of foo()){ console.log(v); }
yield* expression
-
If you call another Generator function in a Generator, the default is no effect;
function* foo(){ yield 'a'; yield 'b'; } function bar(){ yield 1; foo(); yield 2; } for(let i of bar()){ console.log(i); } >>1 >>2
-
It can be seen that the direct call cannot execute foo() in for...of;
-
At this time, you need to use the yield* statement, so the above code can be modified as follows:
function* foo(){ yield 'a'; yield 'b'; } function bar(){ yield 1; yield* foo(); yield 2; } for(let i of bar()){ console.log(i); } >>1 >>'a' >>'b' >>2
-
If yield* is followed by an array, the members of the array will be traversed:
function* foo(){ yield [1,2,3,4,5]; } foo().next(); //[1,2,3,4,5] function* foo(){ yield* [1,2,3,4,5]; } foo().next(); //{value : 1,done : false}
-
The yield* expression can quickly traverse all the members of the nested array;
function* iterTree(tree){ if(Array.isArray(tree)){ for(let i=0;i<tree.length;i++); yield* iterTree(tree[i]); }else{ yield tree; } } const tree = [1,[2,3],[4,5]]; for(let x of iterTree(tree)){ console.log(x); } >> 1 >> 2 >> 3 >> 4 >> 5
This of the Generator function
-
The iterator returned by the Generator function is an instance of the original Generator function, which also inherits the prototype of the original function;
-
Therefore, the iterator of the Generator function cannot directly access the properties of the original function;
function* foo(){ this.a = 1; } let obj = foo(); console.log(obj.a); //undefined
-
To obtain the properties of the original function, you can obtain it through a flexible method-use the call method to bind this inside the Generator function;
function* foo(){ this.a = 1; yield this.b = 2; yield this.c = 3; } let obj = { }; let bar = foo.call(obj); bar.next(); //{value : 2 , done : false}; bar.next(); //{value : 3 , done : false}; console.log(obj.a) // 1 console.log(obj.b) // 2 console.log(obj.c) // 3