Understanding and using Generator functions

Generator functions are an asynchronous programming solution provided by ES6.

Asynchronous programming

1, the so-called “asynchronous”, simply put, a task is divided into two parts, first perform the first part, then move to other tasks, and when ready, back to the second part.

2. Asynchronous programming mode:

(1) callback functions (2) event listeners (3) Publish/subscriber (4) Promise objectsCopy the code

3. The so-called callback function is to write the second paragraph in a separate function, and wait until the task is executed again, call the function directly.

Asynchronous callbacks are prone to multiple nesting. Multiple asynchronous operations form a strong coupling, and if one operation needs to be modified, both its upper and lower callback functions may need to be modified. This is known as callback hell.

The Promise object solves the callback hell problem by allowing the nesting of callback functions instead of chain calls.

What is Generator?

Syntactically, a Generator function is a state machine that encapsulates multiple internal states.

Formally, a Generator function is an ordinary function.

The entire Generator function is an encapsulated asynchronous task, or a container for asynchronous tasks, and uses yield statements whenever asynchronous operations need to be paused.

Features of Generator functions:

(1) There is an asterisk (*) between the function keyword and the function, and the internal yield expression is used to define different internal states.

(2) After invoking the Generator function, the function does not execute and returns a pointer object to the internal state rather than the result of the function’s operation.

Define the Gernerator function

function* fn(a){// Define a Generator function yield'hello';
    yield 'world';
    return 'end'; } var f1 =fn(); // Call the Generator function console.log(f1); // fn {[[GeneratorStatus]]: "suspended"}
console.log(f1.next()); // {value: "hello", done: false}
console.log(f1.next()); // {value: "world", done: false}
console.log(f1.next()); // {value: "end", done: true}
console.log(f1.next()); // {value: undefined, done: true}Copy the code

However, when a Generator function is called, it does not execute and returns not the result of the function’s execution, but a pointer object to the internal state.

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.

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

The pause effect of Generator functions means that asynchronous operations can be written in yield statements 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 statement until the next method is called anyway. Therefore, an important practical use of Generator functions is to handle asynchronous operations and override callback functions.

Yield expressions and next() methods

The Generator returns an iterator object that only calls the next method through the next internal state, so it provides a function to pause execution.

The yield expression is the pause flag.

The expression following the yield expression is executed only when the next method is called and an internal pointer points to the statement.

Use yield to note:

(1) The yield statement can only be used in the scope of function*. If function* defines other ordinary functions inside, the yield statement is not allowed inside the function.

(2) If the yield statement participates in an operation, it must be enclosed in parentheses.

The difference between the return method and the next method:

1) Return terminates iteration, and all subsequent yield statements are invalidated; Next returns the return value of this yield statement. 2)return {value: undefined, done: true}; Next returns the yield statement if there are no arguments. Return {value: argument, done: true}; When next has arguments, it overwrites the return value of the last yield statement, which may or may not be related to the argument (if the argument is evaluated).Copy the code

The next method running logic for the traverser object:

(1) When a yield expression is encountered, the following operation is paused, and the value immediately following the yield expression is used as the value of the returned object's value property. (2) The next time the next method is called, the execution continues until the next yield expression is encountered. (3) If no new yield expression is encountered, the function is run until the end of the return statement, and the value of the expression following the return statement is used as the value of the returned object's value property. (4) If the function does not have a return statement, the value attribute of the returned object is undefined.Copy the code

The next() method argument represents the return value of the previous yield expression, so the passing argument is invalid the first time the next method is used. The V8 engine ignores arguments from the first use of the next method, which are valid only from the second use of the next method. Semantically, the first next method is used to start the traverser object, so it doesn’t take arguments.

The for… The of loop automatically iterates over the Iterator generated by the Generator function, and there is no need to call the next method. Once the done property of the object returned by the next method is true, for… The of loop terminates and does not contain the return object.

Examples understand Generator:

//Define a Generator functionfunction* 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} The second time the next method is run without arguments, resulting in y equals2 * undefinedNaN, divided by NaN3It's still NaNconsole.log(a.next()); // Object{value:NaN, done:false} the third time we run the Next method with no arguments, so z equalsundefined, returns the value property of the object equal to5 + NaN + undefinedThat NaN.console.log(a.next()); // Object{value:NaN, done:true}

var b = foo(5);
console.log(b.next());   // {value:6.done:false} The first time b's next method is called, x+ is returned1The value of the6
console.log(b.next(12)); // {value:8.done:false} the next method is called a second time, setting the value of the last yield expression to12So y is equal to24, return y /3The value of the8;console.log(b.next(13)); // {value:42.done:true} the next method is called a third time, setting the value of the last yield expression to13So z is equal to13And then x is equal to5, y is equal to the24, soreturnThe value of the statement is equal to42.Copy the code

5. Example of using Genarator function

1. Output Fibonacci sequence

function *fibonacci(a){
    let [pre, cur] = [0.1];
    for(;;) { [pre, cur] = [cur, pre+cur];yieldcur; }}for(let n of fibonacci()){
    if( n>1000 )
        break;
    console.log(n);
}Copy the code

2. Traverse the complete binary tree

function* preOrder(root){ // preorder traversal
    if(root){
        yield  root.mid;
        yield* preOrder(root.left);
        yield* preOrder(root.right); }}function* inOrder(root){  // middle order traversal
    if(root){
        yield* inOrder(root.left);
        yield  root.mid;
        yield* inOrder(root.right); }}function* postOrder(root){ // after the sequence traversal
    if(root){
        yield* postOrder(root.left);
        yield* postOrder(root.right);
        yieldroot.mid; }}function Node(left, mid, right){  // binary tree constructor
    this.left = left;
    this.mid = mid;
    this.right = right;
}
function binaryTree(arr){         // Generate a binary tree
    if(arr.length == 1) {return new Node(null, arr[0].null);
    }
    return new Node(binaryTree(arr[0]), arr[1], binaryTree(arr[2]));
}

// Complete binary tree node
let bTree = binaryTree([[['d'].'b'['e']], 'a'The [['f'].'c'['g']]]);

// Iterate over the result
var preResult = [];
for(let node of preOrder(bTree)){  // The result of the preceding traversal
    preResult.push(node);
}
console.log(preResult);            // (7) ["a", "b", "d", "e", "c", "f", "g"]

var inResult = [];
for(let node of inOrder(bTree)){   // Order traversal result
    inResult.push(node);
}
console.log(inResult);              // (7) ["d", "b", "e", "a", "f", "c", "g"]

var postResult = [];
for(let node of postOrder(bTree)){  // the result of the sequence traversal
    postResult.push(node);
}
console.log(postResult);            // (7) ["d", "e", "b", "f", "g", "c", "a"]Copy the code

Genarator reads the text file line by line

function* readFileByLine(a){
    let file = new FileReader("a.txt");
    try{
        while(! file.eof){yield parseInt(file.readLine(), 10); // Use yield expressions to manually read files line by line}}finally{ file.close(); }}var r = readFileByLine();
r.next();Copy the code

Genarator deploys Ajax operations

function* main(a){ // Get data through Ajax operations
    var result = yield request("http://some.url");
    var res = JSON.parse(result);
    console.log(res.value);
}
function request(url){
    makeAjaxCall(url, function(res){it.next(res); })}var it = main();
console.log(it.next());Copy the code

Genarator deploys the Iterator interface to any object

function* deployObjectInterface(obj){
    let keys = Object.keys(obj);
    for(let i=0; i<keys.length; i++){
        let key = keys[i];
        yield[key, obj[key]]; }}let obj = {name:"Cynthia", age:21 };
for(let[key, value] of deployObjectInterface(obj)){
    console.log(key, value); 
}
// name Cynthia
// age 21Copy the code

Genarator deploys the Iterator interface to arrays

function* deployArrayInterface(arr){
    var nextIndex = 0;
    while(nextIndex < arr.length){
        yield arr[nextIndex++];
    }
}
var arr = deployArrayInterface(['name'.'age']);

console.log(arr.next());       // {value: "name".done: false}
console.log(arr.next().value); // name
console.log(arr.next().done);  // false

console.log(arr.next().value); // age
console.log(arr.next().done);  // true

console.log(arr.next().value); // undefined
console.log(arr.next().done);  // trueCopy the code

PS: Refer to the article es6.ruanyifeng.com/#docs/gener…