Understand the principle of Generator function step by step

Author: Mai le

Source: Hang Seng LIGHT cloud community

Basic usage of Generator function

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
 
var hw = helloWorldGenerator();

After the Generator function is called, an iterator object is generated. You can control the execution of the internal code of the function by calling the next method of the iterator.

hw.next()
// { value: 'hello', done: false }
 
hw.next()
// { value: 'world', done: false }
 
hw.next()
// { value: 'ending', done: true }
 
hw.next()
// { value: undefined, done: true }

If the Generator function encounters a yield, you can pause the execution of code inside the Generator function to suspend it. Calling the next() method on an iteratable object allows the code to continue execution from where it was paused.

Let's first understand what is an iteratable object?

Iteratable object

To be an iteratable object, an object must implement the < strong > @ @ iterator < / strong > method. This means that the object (or it Prototype chain An object on) must have a property with a key of @ @ iterator, which can be passed through a constant Symbol.iterator To access this property:

[Symbol.iterator ]An parameterless function whose return value is a match iterator protocol Object.

let someString = "hi";
typeof someString[Symbol.iterator];   
let iterator = someString[Symbol.iterator]();
iterator + "";                               // "[object String Iterator]"
 
iterator.next();         // { value: "h", done: false }
iterator.next();         // { value: "i", done: false }
iterator.next();         // { value: undefined, done: true }

Let's take a look again. How is the hang back? There is a new term "synergetic process".

What is a collaborative process?

Coroutines are lighter than threads. Just as a process can have multiple threads, a thread can have multiple coroutines.

The cooperation process is not managed by the operating system kernel, but is completely controlled by the program, that is, it is executed in the user state. The advantage of this is that the performance is greatly improved, because it will not consume resources like thread switching.

A coroutine is neither a process nor a thread, but a special function. This function can be suspended somewhere and can continue to run outside the suspension again. Therefore, compared with processes and threads, collaborative process is not a dimensional concept.

Principle of Generator function

The Generator function is the implementation of the coroutine in ES6. The biggest feature is that it can hand over the execution right of the function (i.e. suspend execution).

To sum up:

  • There are multiple coroutines in a thread
  • The Generator function is the implementation of the coroutine in ES6
  • Yield suspends the collaboration process (gives it to other collaboration processes), and next evokes the collaboration process

At this point, you should have a new understanding of the Generator function. In actual development, it is not common to directly use the Generator function, because it can only realize the sequential execution of the internal code of the function by manually calling the next method. If you want to use it well, you can implement an automatic execution artifact for the Generator function.

Automatically executed Generator function

The Generator function can be recursively self executed according to the value of done in the return value {value: '', done: false} of g.next():

 function run(generator) {
            var g = generator();

            var next = function() {
                var obj = g.next()
                console.log(obj)
                if(!obj.done) {
                    next()
                }
            }
            next()
        }
        run(helloWorldGenerator)

Writing in this way can realize the self-execution function, but the execution order cannot be guaranteed. Simulate two asynchronous requests:

    function sleep1() {
            return new Promise((resolve) => {
                setTimeout(() => {
                    console.log('sleep1')
                    resolve(1)
                }, 1000)
            })
        }
        function sleep2() {
            return new Promise((resolve) => {
                setTimeout(() => {
                    console.log('sleep2')
                    resolve(2)
                }, 1000)
            })
        }

Modify function

function* helloWorldGenerator() {
            yield sleep1();
            console.log(1);
            yield sleep2();
            console.log(2);
        }

Execute run (Hello World generator) to see the printing order:

Asynchronous functions are executed after the synchronous code is executed. If you want to implement asynchronous code, you can also execute it in order. You can further optimize the code:

        function run(generator) {
            var g = generator();

            var next = function() {
                var obj = g.next();
                console.log(obj)
                if(obj.done) return;
                // If the return value after yield is not promise, promise can be used Resolve the package to prevent error reporting
                Promise.resolve(obj.value).then(() => {next()})
            }
            next()
        }

If sleep1 is a network request, you can get the data returned by the request after the yield. It looks like a more elegant solution to the asynchronous problem.

If you want to get the value of sleep function resolve, it can also be implemented.

// Modify the function variable to receive the result returned by the yield statement
function* helloWorldGenerator() {
            var a = yield sleep1();
            console.log(a);
            var b = yield sleep2();
            console.log(b);
        }
// g.next(v);  Pass result value
      function run(generator) {
            var g = generator();

            var next = function(v) {
                var obj = g.next(v);
                console.log(obj)
                if(obj.done) return;
                // If the return value after yield is not promise, promise can be used Resolve the package to prevent error reporting
                Promise.resolve(obj.value).then((v) => {next(v)})
            }
            next()
        }

You will see the same print as above.

The above implementation is very similar to async await. In fact, this is the principle of async await.

async await

async await is essentially a self executing Generator function implemented in combination with promise. Replace the asterisk (*) of the Generator function with async, and replace yield with await, that's all.

The more detailed code is as follows. Interested students can learn more about it:

 // The async function is to replace the asterisk (*) of the Generator function with async and the yield with await, that's all
            // The async function returns a Promise object. You can use the then method to add a callback function. When the function is executed, once await is encountered, it will return first, wait until the asynchronous operation is completed, and then execute the following statements in the function body
            // The execution of the Generator function must depend on the executor, so there is the co module, and the async function has its own executor, which is executed like an ordinary function
            // The async function improves the Generator function:
            /*
            (1)Built in actuator
            (2)Better semantics
            (3)Wider applicability
            (4)The return value is Promise
            */
 
  
            // The implementation principle of async function is to wrap the Generator function and automatic actuator in one function.
            async function fn(args) {
                // ...
            }
            // It is equivalent to replacing await with yield
            function fn(args) {
                return spawn(function*() {
                    // ...
                });
            }
  
            function spawn(genF) {
                return new Promise(function(resolve, reject) {
                    const gen = genF();
                    function step(nextF) {
                        let next;
                        try {
                            next = nextF();
                        } catch (e) {
                            return reject(e);
                        }
                        if (next.done) {
                            return resolve(next.value); // This is why if you do not use try catch, the code behind the abnormal asynchronous request will not be executed. From here, you will not continue to call the nex() method.
                        }
                        Promise.resolve(next.value).then(function(v) {
                            step(function() {
                                return gen.next(v);
                            });
                        }, function(e) {
                            step(function() {
                                return gen.throw(e); // Here we can explain why the async function needs to use try catch to catch exceptions. The throw of the generator function will let the code into the catch
                            });
                        });
                    }
                    step(function() {
                        return gen.next(undefined);
                    });
                });
            }

Want to learn more from technology bosses? Where to discuss the problems encountered in development? How to obtain massive financial technology resources?

Hang Seng LIGHT cloud community , the financial technology professional community platform built by Hang Seng electronics shares practical technology dry goods, resource data and financial technology industry trends, and embraces all financial developers.

Scan the QR code of the applet below and join us!

Keywords: Javascript Front-end generator

Added by HGeneAnthony on Tue, 11 Jan 2022 09:11:26 +0200