Basic concept: Asynchronous programming solution

Generator functions are an asynchronous programming solution provided by ES6 with completely different syntactic behavior from traditional functions.

1. Grammar:GeneratorThe function is aThe state machineEncapsulates multiple internal states

Executing Generator returns an traverser object

In addition to the state machine, the Generator function is also an iterator object Generator

Execution of Generator returns an traverser object, that is, Generator is also a traverser object Generator in addition to the state machine. A traverser object that iterates through each state within the Generator in turn.

2. Form:GeneratorA function is an ordinary function, but it has two characteristics

(1) Function keyword and function name between oneThe asterisk;

(2) Internal use of function bodyyieldExpression that defines different internal states (yieldIn English, it means"Output")

3, callGeneratorAfter the function:You must callOf the traversal objectNext methodTo move the pointer to the next state

This function does not execute and does not return the result of a function run. Instead, it returns a pointer Object to the internal state, the Iterator Object described in the previous chapter.

(1) Each callnextMethod, the inner pointer executes from the head of the function or where it last stopped until the next one is encounteredyieldExpression (orreturnStatement) until

(2) The Generator functions are executed piecewise, the yield expression is a token to pause execution, and the next method can resume execution

Next, the next method of the traverser object must be called to move the pointer to the next state. That is, each time the next method is called, the internal pointer executes from the head of the function or where it was last stopped until the next yield expression (or return statement) is encountered. In other words, Generator functions are executed piecewise, yield expressions are paused tokens, and the next method can resume execution.

function* aaa(){
    yield 'hello';
    yield 'world';
    return 'ending';
}

var hw=aaa();
console.log(aaa);
//[GeneratorFunction: aaa]

console.log(hw);
//Object [Generator] {}

console.log(hw.next());
// { value: 'hello', done: false }

console.log(hw.next());
// { value: 'world', done: false }

console.log(hw.next());
// { value: 'ending', done: true }

console.log(hw.next());
// { value: undefined, done: true }
Copy the code

The above function has three states: hello, world, and return statements (end execution).

(3)nextMethod returns an object whosevalueThe property is the currentyieldThe value of the expression,doneThe value of the property indicates that the traversal is not complete

(4) UntilReturn statement(ifNo return statement, they performAt the end of the function)

(5)nextThe object returned by theThe value attribute, is to keep up withAfter the return statementThe value of the expression of (ifThere is no returnStatements,valueThe value of the attributeFor undefined)

(6)doneThe value of the attributeTo trueIs displayed, indicating that the traversal is complete

4. The asterisk between the function keyword and the function name is not specified

/ / will do
function * foo(x, y) {...}function *foo(x, y) {...}function* foo(x, y) {...}function*foo(x, y) {...}Copy the code

Yield expression

Because the Generator returns a traverser object that only calls to the next method iterate over the next internal state, it actually provides a function to pause execution.

The yield expression is the pause flag

Run logic for the next method of the traversal object

(1) EncounteryieldThe expression isPause subsequent operationsAnd will followyieldThe value of the following expression as the value of the returned objectvalueAttribute values

(2) Next callnextMethod, and then continue untilThe next yield is encounteredexpression

(3) IfThere is noMeet againThe new yieldThe expression isRun until the end of the functionUntil thereturnStatement, and willreturnThe value of the expression following the statement as the value of the returned objectvalueAttribute values

(4) If the functionThere is no returnStatement of the object returnedValue The value of the attribute is undefined

(5)yieldexpressionThe following expression, only when callednextMethod, an internal pointer to the statementWill performSo it’s equal to zeroJavaScriptManual is provided"Lazy evaluation"(Lazy Evaluation) syntax capabilities

function* gen() {
  yield  123 + 456;
}
//yield 123 + 456 is not evaluated immediately, but only when the next method moves the pointer to this line.
Copy the code

Yield expressions and return statements

There are similarities and differences.

The similarity is that: can return the value of the expression immediately following the statement

The difference is that: Every timeyield, the function suspends execution and continues backward from that position the next time, whilereturnStatements do not have the function of location memory

Inside a function, only executeReturn at a time (or one)Statement, but can be executedYield several times (or more)expression

Without yield expressions, the Generator function becomes a pure deferred function

function* f() {
  console.log('Executed! ')}var generator = f();

setTimeout(function () {
  generator.next()
}, 2000);
Copy the code

Yield expressions can only be used in Generator functions

If you use it anywhere else, you’ll get an error.

(function (){
  yield 1; }) ()// SyntaxError: Unexpected number
Copy the code

If the yield expression is used in another expression, it must be enclosed in parentheses

function* demo() {
	//console.log('Hello' + yield); // SyntaxError
	//console.log('Hello' + yield 123); // SyntaxError
    
    console.log('Hello' + (yield)); // OK
    console.log('Hello' + (yield 123)); // OK
}

var a=demo();
console.log(a.next());  //{ value: undefined, done: false }
//Helloundefined

console.log(a.next());  //{ value: 123, done: false }
//Helloundefined

console.log(a.next());//{ value: undefined, done: true }
Copy the code

Yield expressions are used as function arguments or to the right of assignment expressions, without parentheses

var foo=function(a,b){
    console.log(a);
    console.log(b);
}
// Demo has three yields, so it takes four next done times to equal true
function* demo() {
    foo(yield 'a'.yield 'b'); // OK
    let input = yield; // OK
    console.log(input);
}

var a=demo();
console.log(a.next());  //{ value: 'a', done: false }

console.log(a.next());  //{ value: 'b', done: false }

console.log(a.next());  // This step is executed by foo
						//undefined
                        // undefined
                        // { value: undefined, done: false }

console.log(a.next());  //undefined
                        // { value: undefined, done: true }

Copy the code

Relationship to the Iterator interface

1. Of any objectSymbol.iteratorMethod,Is equal to theThe object ofThe traverser generates functions, calling the function willreturnThe object ofAn traverser object

2, due to theGeneratorfunctionThat's the traverser generator function, so we can putGenerator Assigned to the objectSymbol.iteratorAttribute, thusmakeHave toThis object has an Iterator interface

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] / / [1, 2, 3]
Copy the code

3,GeneratorThe function returns an iterator object after execution. The object itself also hasSymbol.iteratorProperties,Symbol.iteratorProperty after executionReturn to their own

function* gen(){
  // some code
}

var g = gen();

g[Symbol.iterator]() === g
// true
Copy the code

Parameters to the next method

1.The yield expression itself returns no value, or always returns undefined(This is why yield variables are always undefined.)

2. Code following the last yield (the next line, not the code immediately following the yield) is executed only after the next next

3,nextA method can take an argument that is treated asOn a yieldThe expression of theThe return value(Change the last yield and the statements immediately following yield to arguments to next)

function* f() {
    for(var i = 0; true; i++) {
        var reset = yield i;
        console.log(reset);
        if(reset) { i = -1; }}}var g = f();

console.log(g.next());  // { value: 0, done: false }

console.log(g.next());  //undefined
                        // { value: 1, done: false }

console.log(g.next());  //undefined
                        // { value: 2, done: false }

console.log(g.next(true));    // true
                              //{ value: 0, done: false }

Copy the code

If the next method has no arguments, the value of the reset variable is always undefined each time the yield expression is run.

When the next method takes a parameter true, the reset variable is reset to that parameter (true), so I equals -1 and the next loop increments from -1 to 0.

throughnextThe parameters of the method enter different values at different stages

1.Generator The context state of a function from paused to resumed (context) is constant
2, through thenextMethod parameters, there is a method inGeneratorOnce the function starts running,Continue to inject values into the function body

That is, the behavior of the Generator function can be adjusted by injecting different values from outside to inside at different stages of its operation.

3, due to thenextThe argument to the method represents the return value of the previous yield expression, so inThe first time you use the next method, passing parameters is invalid
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} y=undefined
console.log(a.next());  // Object{value:NaN, done:false} Equal to z = = NaN undefined / 3
console.log(a.next());  // Object{value:NaN, done:true} = 5+NaN+undefined

var b = foo(5);
console.log(b.next());            // { value:6, done:false } y=undefined
console.log(b.next(12));    // {value:8, done:false} y=2*12=24 z=undefined
console.log(b.next(13));    // {value:42, done:true} z
Copy the code

for… The of loop no longer calls the next method

for… The of loop automatically iterates over the Iterator generated when the Generator function is running, and there is no need to call the next method.

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1, 2, 3, 4, 5
Copy the code

Make native objects support for… Of, which is traversed by a Generator function

Native JavaScript objects have no traversal interface and cannot use for… The of loop is ready to be used by adding this interface to it via a Generator function.

function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj);

  for (let propKey of propKeys) {
    yield[propKey, obj[propKey]]; }}let jane = { first: 'Jane'.last: 'Doe' };

for (let [key, value] of objectEntries(jane)) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
Copy the code
willGenerator Function added to the objectSymbol.iteratorProperty above.
function* objectEntries() {
  let propKeys = Object.keys(this);

  for (let propKey of propKeys) {
    yield [propKey, this[propKey]]; }}let jane = { first: 'Jane'.last: 'Doe' };

jane[Symbol.iterator] = objectEntries;

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
Copy the code
Extended operator (.), destruct assignment andArray.fromMethod can be used toGenerator Function returnedIteratorObject as a parameter

Extended operators (…) , destruct assignment, and array. from are all called inside the iterator interface. This means that they can both take an Iterator returned by a Generator function as an argument.

function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

// Extend the operator
[...numbers()] / / [1, 2]

/ / Array. From method
Array.from(numbers()) / / [1, 2]

// Destruct the assignment
let [x, y] = numbers();
x / / 1
y / / 2

// for... Of circulation
for (let n of numbers()) {
  console.log(n)
}
/ / 1
/ / 2
Copy the code

The Generator. The prototype. Throw () function in vitro throw an error, then inGeneratorFunction capture in vivo

Each traverser object returned by a Generator has a throw method that throws an error outside the function and catches it inside the Generator.

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('Internal capture', e); }};var i = g();
i.next();

try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('External capture', e);
}
// Internally capture a
// External capture b
Copy the code

In the code above, the traverser object I throws two consecutive errors. The first error is caught by a catch statement inside the Generator. I Throws an error the second time. Since the catch statement inside the Generator has already been executed and will not be caught again, the error is thrown into the Generator body and caught by the catch statement outside the Generator.

1.throwThe method can take an argument that is calledcatchStatement received, recommended to throwErrorObject instance

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log(e); }};var i = g();
i.next();
i.throw(new Error('Wrong! '));
// Error: Error! (...).
Copy the code

2. Do not confuse the traverser objectthrowMethod and globalthrowThe command

The error in the code above is thrown by the throw method of the traverser object, not by the throw command. An error thrown by a global throw command can only be caught by a catch statement outside the function.

The throw command is independent of the g.row method and does not affect each other.

var g = function* () {
  while (true) {
    try {
      yield;
    } catch (e) {
      if(e ! ='a') throw e;
      console.log('Internal capture', e); }}};var i = g();
i.next();

try {
  throw new Error('a');
  throw new Error('b');
} catch (e) {
  console.log('External capture', e);
}
// External capture [Error: a]
Copy the code

3. Generator functionsinternalNo try is deployed… Catch block, sothrowMethod throws errors that will be externaltry... catchCode block capture

var g = function* () {
  while (true) {
    yield;
    console.log('Internal capture', e); }};var i = g();
i.next();

try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('External capture', e);
}
// External capture a
Copy the code

4. Generator functionsInternal and external, do not deploy try… Catch block then the program will report an error, directly interrupt execution

var gen = function* gen(){
  yield console.log('hello');
  yield console.log('world');
}

var g = gen();
g.next();
g.throw();
// hello
// Uncaught undefined
Copy the code

5. Errors thrown by the throw method must be caught internally if the next method has been executed at least once

function* gen() {
  try {
    yield 1;
  } catch (e) {
    console.log('Internal capture'); }}var g = gen();
g.throw(1);
// Uncaught 1
Copy the code

In the code above, the next method is not executed once when g.row (1) is executed. Instead of being caught internally, the thrown error is thrown directly externally, causing the program to fail. This behavior is easy to understand, because the first execution of next is equivalent to starting the internal code of the Generator function, otherwise the Generator has not started execution, and the throw error can only be thrown outside the function.

6. Once the throw method is caught, the next yield expression is executed

That is, a next method is executed as an accompaniment

As long as the Generator function has a try deployed internally… Catch block, so that an error thrown by the iterator’s throw method does not affect the next iteration.

var gen = function* gen(){
  try {
    yield console.log('a');
  } catch (e) {
    // ...
  }
  yield console.log('b');
  yield console.log('c');
}

var g = gen();
g.next() // a
g.throw() // b
g.next() // c

Copy the code

7. An error thrown by a Generator can also be caught by a catch outside the Generator

function* foo() {
  var x = yield 3;
  var y = x.toUpperCase();
  yield y;
}

var it = foo();

it.next(); // { value:3, done:false }

try {
  it.next(42);
} catch (err) {
  console.log(err);
}
Copy the code

The second next method passes a parameter 42 to the function body, which has no toUpperCase method, so it throws a TypeError that is caught by a catch outside the function body.

Once an error is thrown during Generator execution and is not caught internally, it will not proceed

Once an error is thrown during Generator execution and is not caught internally, it will not proceed.

If the next method is called later, an object with value equal to undefined and done equal to true is returned, indicating that the JavaScript engine considers the Generator to be finished running.

function* g() {
  yield 1;
  console.log('throwing an exception');
  throw new Error('generator broke! ');
  yield 2;
  yield 3;
}

function log(generator) {
  var v;
  console.log('starting generator');
  try {
    v = generator.next();
    console.log('Run the next method for the first time', v);
  } catch (err) {
    console.log('Catch errors', v);
  }
  try {
    v = generator.next();
    console.log('Run the next method a second time', v);
  } catch (err) {
    console.log('Catch errors', v);
  }
  try {
    v = generator.next();
    console.log('Run the next method a third time', v);
  } catch (err) {
    console.log('Catch errors', v);
  }
  console.log('caller done');
}

log(g());
// starting generator
// Next {value: 1, done: false}
// throwing an exception
{value: 1, done: false}
// Next {value: undefined, done: true}
// caller done
Copy the code

This code runs the next method three times, the second time throwing an error, and the third time the Generator is finished.

The Generator. The prototype. The return () returns the given value, and put an end to traverse the Generator function

The traverser object returned by the Generator function, and a return method that returns the given value and terminates the traversal of the Generator function.

1, ifreturnMethod called with no arguments, returns the valuevalueProperties forundefined

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next()        // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next()        // { value: undefined, done: true }
Copy the code

2. After calling return, call next, value is always undefined, and the done attribute always returns true

In the code above, after the iterator object G calls the return method, the value attribute of the return value is the argument foo of the return method. In addition, the Generator’s traversal terminates, and the done attribute returns true. Next calls always return true.

3, ifGenerator Inside the function istry... finallyCode block, and executingtryCode block, soreturnThe method will be deferred tofinallyExecute the code block after it has been executed

function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
Copy the code

In the above code, the finally block is executed after the return method is called, and then the return method is executed after the finally block is finished.

Yield * expression ifyieldThe expression is followed by an traverser object that is required in theyieldSo let’s add to this expressionThe asterisk, indicating that it returns an traverser object

1, inGenerator Function inside, callAnother GeneratorFunction. You need to manually complete the traversal yourself inside the former function body

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

function* bar() {
  yield 'x';
  // Manually traverse foo()
  for (let i of foo()) {
    console.log(i);
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// x
// a
// b
// y
Copy the code

2,ES6 providesyield*The expression, as a solution, is used in aGenerator Inside the functionExecute another Generatorfunction

(1)yield*At the back of theGeneratorFunction (nonereturnStatement), equivalent toGeneratorInside the function, deploy onefor... ofcycle

(2)yield*At the back of theGenerator Function in areturnStatement is requiredvar value = yield* iteratorForm acquisition ofreturnThe value of the statement

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';
}

/ / is equivalent to
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"
Copy the code

From a syntactic point of view, if a yield expression is followed by an traverser object, you need to add an asterisk after the yield expression to indicate that it returns an traverser object. This is called the yield* expression.

function* inner() {
  yield 'hello! ';
}

function* outer1() {
  yield 'open';
  yield inner();
  yield 'close';
}

var gen = outer1()
gen.next().value // "open"
gen.next().value // Returns a traverser object
gen.next().value // "close"

function* outer2() {
  yield 'open'
  yield* inner()
  yield 'close'
}

var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
Copy the code

Yield * is followed by an array (string), which, since arrays (strings) natively support iterators, iterates over group members

// The yield command returns the entire array without an asterisk, and the asterisk indicates that the array traversator object is returned.
function* gen(){
    yield* ["a"."b"."c"];
}
var n=gen();
console.log(n.next()); // { value:"a", done:false }
console.log(n.next()); // { value:"b", done:false }
console.log(n.next()); // { value:"c", done:false }
console.log(n.next()); // { value:undefined, done:true }

The yield expression returns the entire string, and the yield* statement returns a single character. Because strings have an Iterator interface, they are iterated by yield*
function* gen(){
    yield* 'hello';
}
var n=gen();
console.log(n.next()); // { value:"h", done:false }
console.log(n.next()); // { value:"e", done:false }
console.log(n.next()); // { value:"l", done:false }
console.log(n.next()); // { value:'l', done:true }
console.log(n.next()); // { value:'o', done:true }
console.log(n.next()); // { value:undefined, done:true }
Copy the code

A proxied Generator function that has a return statement can be proxied to itsGenerator Function returns data

function* foo(){
    yield 2;
    yield 3;
    return 'foo';
}

function* bar(){
    yield 1;
    var v = yield* foo();
    console.log('v' + v);
    yield 4;
}

var it = bar();

console.log(it.next());     //{ value: 1, done: false }
console.log(it.next());     //{ value: 2, done: false }
console.log(it.next());     //{ value: 3, done: false }
console.log(it.next());     //vfoo
                            //{ value: 4, done: false }
console.log(it.next());     //{ value: undefined, done: true }
Copy the code
function* genFuncWithReturn() {
  yield 'a';
  yield 'b';
  return 'The result';
}
function* logReturned(genObj) {
  let result = yield* genObj;
  console.log(result);
}

[...logReturned(genFuncWithReturn())]
// The result
// The value is ['a', 'b']
Copy the code

There are two traversals. The first is the iterator object returned by the extended operator iterator function logReturned, and the second is the iterator object returned by the yield* statement iterator genFuncWithReturn. The effect of these two traversals is superimposed, culminating in the traversal object returned by the extended operator function genFuncWithReturn. So, the final data expression yields a value equal to [‘a’, ‘b’]. However, The result returned by The return statement of genFuncWithReturn is returned to The result variable inside logReturned, so there is terminal output.

The yield* command is a convenient way to extract all members of a nested array

function* iterTree(tree) {
  if (Array.isArray(tree)) {
    for(let i=0; i < tree.length; i++) {
      yield* iterTree(tree[i]); }}else {
    yieldtree; }}const tree = [ 'a'['b'.'c'], ['d'.'e']].for(let x of iterTree(tree)) {
  console.log(x);
}
// a
// b
// c
// d
// e

Copy the code

A Generator function as an object property

If the property of an object is a Generator function, you can abbreviate it as follows.

let obj = {
  * myGeneratorMethod(){...}};Copy the code

Its full form is as follows and is equivalent to the above.

let obj = {
  myGeneratorMethod: function* () {
    / /...}};Copy the code

This of the Generator function

An instance of a Generator function inherits methods from the Prototype object of the Generator function

Generator functions always return an traverser, which ES6 defines as an instance of Generator functions and inherits methods from Generator function prototype objects.

function* g() {}

g.prototype.hello = function () {
  return 'hi! ';
};

let obj = g();

obj instanceof g // true
obj.hello() // 'hi! '
Copy the code

2. Generator functions cannot be constructors either

If you treat g as a normal constructor, it doesn’t work because g always returns a traverser object, not this.

function* g() {
  this.a = 11;
}

let obj = g();
obj.next();
obj.a // undefined
Copy the code

3. Generator functions cannot be used with the new command, which generates an error

function* F() {
  yield this.x = 2;
  yield this.y = 3;
}

new F()
// TypeError: F is not a constructor
Copy the code

4. Have the Generator return a normal instance of an object, either using the next method or a normal this

function* gen() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}

function F() {
  return gen.call(gen.prototype);
}

var f = new F();

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}

f.a / / 1
f.b / / 2
f.c / / 3
Copy the code

Generator and state machine

The Generator is the best structure for implementing a state machine.

The following clock function is a state machine

//es5
var ticking = true;
var clock = function() {
  if (ticking)
    console.log('Tick! ');
  else
    console.log('Tock! '); ticking = ! ticking; }//es6
// The loop generates Tick and Tock because while is always true
var clock = function* () {
    while (true) {
        console.log('Tick! ');
        yield;
        console.log('Tock! ');
        yield;
    }
}();
console.log(clock.next());
console.log(clock.next());
console.log(clock.next());
console.log(clock.next());
// Tick!
// { value: undefined, done: false }
// Tock!
// { value: undefined, done: false }
// Tick!
// { value: undefined, done: false }
// Tock!
// { value: undefined, done: false }

Copy the code

application

(1) Synchronous expression of asynchronous operation

The pause effect of Generator functions means that asynchronous operations can be written in yield expressions and executed later when the next method is called. This is essentially equivalent to not having to write the callback function, because subsequent operations of the asynchronous operation can be put under the yield expression until the next method is called anyway. Therefore, an important practical use of Generator functions is to handle asynchronous operations and override callback functions.

function* loadUI() {
  showLoadingScreen();
  yield loadUIDataAsynchronously();
  hideLoadingScreen();
}
var loader = loadUI();	// The loadUI function is called without actually doing anything
/ / load the UI
loader.next()			// First time next

/ / uninstall the UI
loader.next()			// Next
Copy the code

The first time the loadUI function is called, it does not execute and only returns a traverser.

The next time you call the next method on this traverser, the Loading interface (showLoadingScreen) is displayed and data is loaded asynchronously (loadUIDataAsynchronously).

When the data is loaded and the next method is used again, the Loading interface will be hidden.

The Generator function deploys Ajax operations

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);   // Next must have an argument otherwise result in main is undefined
  });
}

var it = main();
it.next();
Copy the code

Read the text file line by line through the Generator function

function* numbers() {
  let file = new FileReader("numbers.txt");
  try {
    while(! file.eof) {yield parseInt(file.readLine(), 10); }}finally{ file.close(); }}Copy the code

(2) Control flow management

If you have a multi-step operation that is very time consuming.

The callback function

step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
      step4(value3, function(value4) {
        // Do something with value4
      });
    });
  });
});
Copy the code

Promise

Promise.resolve(step1)
  .then(step2)
  .then(step3)
  .then(step4)
  .then(function (value4) {
    // Do something with value4
  }, function (error) {
    // Handle any error from step1 through step4
  })
  .done();
Copy the code

The Generator function

function* longRunningTask(value1) {
  try {
    var value2 = yield step1(value1);
    var value3 = yield step2(value2);
    var value4 = yield step3(value3);
    var value5 = yield step4(value4);
    // Do something with value4
  } catch (e) {
    // Handle any error from step1 through step4}}Copy the code

Then, all the steps are automatically executed in sequence using a function.

scheduler(longRunningTask(initialValue));

function scheduler(task) {
  var taskObj = task.next(task.value);
  // Continue if Generator is not finished
  if (!taskObj.done) {
    task.value = taskObj.value
    scheduler(task);
  }
}
Copy the code

This is only applicable to synchronous operations, that is, all tasks must be synchronous and no asynchronous operations can be performed. Because the code here continues as soon as it gets the return value, it doesn’t know when the asynchronous operation is complete.

Use for… The of loop automatically executes the yield feature in turn, providing a more general approach to control flow management

(Can only be used when all steps are synchronous)

// Array steps encapsulates the steps of a task
let steps = [step1Func, step2Func, step3Func];

// The Generator function iterateSteps adds yield to each step in turn
function* iterateSteps(steps){
  for (var i=0; i< steps.length; i++){
    var step = steps[i];
    yieldstep(); }}/ / for... The of loop executes all the steps of each task in sequence at once
for (var step of iterateJobs(jobs)){
  console.log(step.id);
}
Copy the code
for... ofThe essence of awhileLoop, so the above code essentially executes the following logic
var it = iterateJobs(jobs);
var res = it.next();

while(! res.done){var result = res.value;
  // ...
  res = it.next();
}
Copy the code

(3) UseGenerator The delta function can be atAny objectDeployed onIteratorinterface

function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield[key, obj[key]]; }}let myObj = { foo: 3.bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
  console.log(key, value);
}

// foo 3
// bar 7
Copy the code

Deploy the Iterator interface to arrays

function* makeSimpleGenerator(array){
  var nextIndex = 0;

  while(nextIndex < array.length){
    yieldarray[nextIndex++]; }}var gen = makeSimpleGenerator(['yo'.'ya']);

gen.next().value // 'yo'
gen.next().value // 'ya'
gen.next().done  // true
Copy the code

(4) As a data structure

GeneratorCan be seen asThe data structureTo be more precise, it can be regarded as aArray structure

Because a Generator function can return a series of values, this means that it can provide an array-like interface to any expression.

function* doStuff() {
  yield fs.readFile.bind(null.'hello.txt');
  yield fs.readFile.bind(null.'world.txt');
  yield fs.readFile.bind(null.'and-such.txt');
}
Copy the code

The above code returns three functions in sequence, but using Generator functions allows you to process the three returned functions as if they were arrays.

for (task of doStuff()) {
  // Task is a function that can be used like a callback function
}

// Equivalent to ES5
function doStuff() {
  return [
    fs.readFile.bind(null.'hello.txt'),
    fs.readFile.bind(null.'world.txt'),
    fs.readFile.bind(null.'and-such.txt')]; }Copy the code