Your ambition, learn your perseverance! Your ambition, learn your perseverance!

I. Generator functions

1. Basic concepts and usage

Generator function is a new asynchronous solution proposed in ES6, which is different from ordinary functions. Generator functions must be preceded by * and have a yiled pause flag inside the function. We can think of a Generator as a state machine in which many asynchronous operations can exist. The result of this function execution is not a return value, but a traverser object. By executing the next method on the traverser object, you can change the state pointer inside the function to iterate over each state inside the Generator function. First, let’s take a look at the basic writing of this function:

function* gen() {
    yield 1; // yield pauses the execution of the flag
    yield 2;
    yield 3;
    yield 4;
}
const iterater = gen(); // Iterater is an iterater,
                        // The internal state of a generator function can be traversed by its next method
iterater.next(); // {value : 1, done : false}
iterater.next(); // {value : 2, done : false}
iterater.next(); // {value : 3, done : false}
iterater.next(); // {value : 4, done : false}
iterater.next(); // {value : undefined, done : true}
Copy the code
Iterater execution mechanism: By calling iterater's next method, the internal pointer to a generator function is moved to the next state, starting from the head of the function or from where it was last paused until the next yield or return statement. Generator functions are executed piecewise, with yield suspending execution and next resuming execution.Copy the code

Although the yield and return expressions are used as the value attribute of the next() function after execution, they are different. The function considers traversal completed when it encounters return, and only pauses when it encounters yiled. The next traversal occurs

2. Attention points of the next method’s parameter passing

The next method is passable, and the parameters passed by the next method are returned by the yield expression. If the next method is executed with no parameters, then the last yiled expression is undefined. The first next execution has invalid parametersCopy the code
function* gen() {
   var val1 = yield 1;
   console.log(val1)   / / 2
   var val2 = yield 2;
   console.log(val2)   / / 3
   var val3 = yield val2 + 3;
   console.log(val3)   / / 4
   var val4 = yield 4;
   console.log(val4)   / / 5
}
const iterater = gen(); 
iterater.next(1); {value: 1, done: false}
iterater.next(2); //{value: 2, done: false}
iterater.next(3); //{value: 6, done: false}
iterater.next(4); //{value: 4, done: false}
iterater.next(5); //{value: undefined, done: true}
Copy the code

3. Use of Generator functions (to generate iterators)

for... Inside the of loop is the Iterator interface deployed on the data type that needs to be iterated over. This interface is the next method that automatically calls the Iterator. We can use generator functions to try to add this property to objects that can also pass for... Of traversal

In the same way, extension operators... , destruct assignment, and array. from are all internal calls to the traverser interface

// Implement the iterator interface of the object via generator
const obj = {
    'key1': 1.'key2': 2.'key3': 3[Symbol.iterator] : function* (){ // We implement an iterator using a generator function
        // Since the object itself has no subscript and no order, we can convert the object to map
        const map = new Map(a); map.set('key1'.1)
                map.set('key2'.2)
                map.set('key3'.3)
                console.log(map)
                // 0: {"key1" => 1}
                // 1: {"key2" => 2}
                // 2: {"key3" => 3}
                // size: 3
         console.log([...map.entries()]) 
       // the map.entries() method returns an iterable object
       / /... The operator is actually executed by the iterator method of the object
        const mapResult = [...map.entries()];
        var index = 0;
        var size = map.size;
        while(index < size){
            yieldmapResult[index++]; }}}// for... Of automatically calls the next method on the iterator
    for(let item of obj){
        console.log(item) // ["key1", 1] ["key2", 2] ["key3", 3]
    }
      / /! What needs special attention is: Of stops traversal when next returns the done property of the object to true,
      // And does not contain the value corresponding to done true
Copy the code

Two, the actuator

While generator functions are a good asynchronous solution that allows us to handle asynchronous logic as if we were writing synchronous code, we can find that it is not business-friendly to manually execute the generation iterator each time and manually call the next method to iterate. So we need an actuator to automatically traverse all the states for us.

function readFile(url){
    return new Promise(function(resolve,reject){
        fs.readFile(url,function(error,data){
            if(error) reject(error);
            resolve(data)
        })
    })
}

function* gen(){
    var f1 = yield readFile('./a.txt');
    console.log(f1)
    var f2 = yield readFile('./b.txt');
    console.log(f2)
   
}
function co( generator ){
    const iterator = generator();
    return new Promise(function(resolve,reject){
      // Iterate through the internal state recursively
        function diff(value){
            ret =  iterator.next(value);
            if(ret.done) return resolve(ret.value);
            Promise.resolve(ret.value).then(function(data){
                    diff(data.toString())
             });
        }
        try{
            diff()
        }catch(err){
            reject(err)
        }
    })

}
co(gen)
Copy the code
The CO module is an executor module implemented by itself. The CO method receives a generator function as a parameter. The iterator will be automatically generated in CO according to the constructor function, and each state will be automatically iterated to get the asynchronous result. Assign the value of each yiled result to the corresponding Yiled result. Finally, we print the contents of the corresponding file in the generator functionCopy the code

After executing a generator with an executor, we will see that generator async is now written in a similar way to async. Async is the syntax-equivalent of a generator with an executor module

Third, async await

With this in mind, it is easy to understand async as it is a generator with an executor and is more semantic than generator. You can get the result of an expression behind “await” without having to execute iterators manually.

1. Use of async functions

The return value of async is a promise, which can be used with. Then. If the function has no return value, the values are undefiend.

    // async is used to identify async functions
    async function getInfo(){
     // await asynchronous execution, return result and continue execution
      var result1 = await  ajax('https://api.github.com/users/github');
     console.log(result)
     var result2 = await  ajax('https://api.github.com/users/github');
     console.log(result2)
}

// The function is called just like a normal function, only need to call once, multiple asynchrony in the function body will execute at once
var pro = getInfo()
pro.then(function(data){
    console.log(data) //undefined
})
Copy the code

2. The returned PROMISE state changes after async execution

When an async function is executed, it immediately returns a Promise object, and changes only when all Promise objects behind await have been executed, except when a return statement or code execution error is encountered. That is, the callback to the THEN method is executed only after all asynchronous operations in the function body have been executed.

3.await

An await command can be followed by a Promise object and a value of the primitive type (numeric, string, and Boolean, but this is equivalent to a synchronous operation), and if it is not followed by a Promise object, it is automatically converted to an immediately resolve Promise object

    async function asy(){
      var res1 = await 3; } ==> Equivalent to:async function asy(){
      var res1 = await Promise.resolve(3);
    }
Copy the code

If the Promise after the await command changes to rejected, then the Promise returned by async will also change to Rejected

async function asy(){
    await Promise.reject('error')
   const f2 =  await readFile('./a.txt') // The following code will not be executed
}
asy().then(function(data){
    console.log(data)
}).catch(function(err){
    console.log(err) //error
})
Copy the code
  • Since the Promise may run in the Rejected state, it is better to put await statements in the try statement to prevent an asynchronous operation from failing. In the catch
async function asy(){
    let f2;
    try {
        await Promise.reject('error')}catch (error) {
        console.log(error)
    }
    f2 =  await readFile('./a.txt')
      return f2
}
asy().then(function(data){
    console.log(data) // The result of f2 will be printed
}).catch(function(err){
    console.log(err)
})
Copy the code
  • Asynchronous operations followed by multiple await operations should be fired at the same time if there is no secondary relationship. This will speed up execution
async function asy(){
    // let f1 = await readFile('./a.txt')
    // let f2 = await readFile('./b.txt')
    let rf1 =  readFile('./a.txt')
    let rf2 =  readFile('./b.txt')
    let f1 = await rf1;
    let f2 = await rf2;
}
Copy the code

This article about async Generator is shared here. Please give a thumb-up to your favorite friends and point out your shortcomings in the comments. Learn together and make progress together!