In the Promise section I mentioned async/await and generator. I promised you an update, and today it’s here.

The Generator function

We must talk about generator before we can talk about async/await, because async/await is the syntactic sugar of generator. As everyone at the front end knows, The Generator has the following features:

  • There is an asterisk “*” between the function keyword and the function name.
  • Function bodies use yield expressions to define different internal states (there can be multiple yields).
  • Direct callGeneratorThe function does not execute and does not return the result of the run, but returns oneTraverser object(Iterator Object).
  • To call the traverser object in turnnextMethod, traversalGeneratorEvery state inside the function.

I don’t think that’s enough, but let’s take a look at what’s in this function after we declare it:

  1. First of all, we haveprototypeUnder the__proto__Found a method to mount under the instancenext().return().throw(). These methods are definitely something we have to figure out. Keep looking down.
  2. Ordinary functional__proto__The followingconstructorAre allFunction(), indicating that is byFunctionInstantiate out of the object, but its isGeneratorFunction(). That’s a problem.

So far these two problems have been identified by this picture. Let’s tackle the first one first, but before that I want to make a brief introduction as a basis. In normal functions, we want the final result of a function, and we usually return it, or we end the function with a return. A function cannot be interrupted while it is running, nor can values be passed in from outside the function. However, this is broken in Generator, so it is completely different from other functions. When a Generator is declared as function*, it is possible to have a number of states inside, with yield separating breakpoints. We call the generated Generator, which returns an iterator object and uses methods on the object to achieve a yield output.

function* generator() {
    yield 1
    yield 2
};
let iterator = generator();
iterator.next()  // {value: 1, done: false}
iterator.next()  // {value: 2, done: false}
iterator.next()  // {value: undefined, done: true}
Copy the code

Methods on Generator instances

next()

Next () is called to iterate over the traverser object, and each time it is called, it returns an object:

  1. valueValue is the internalyieldThe return value of an expression.
  2. There is also a value indicating whether execution is completedone.next()I can’t find the backyieldKeyword, the return value istrue“And because we didn’t find the last oneyieldExpression, sovalueforundefined.
  3. If theyieldIt was executed beforereturn, so go over toreturnIs returned{value: undefined, done: true}No matter how it is iterated later, it will not perform the heat afteryield.

2) We can also pass arguments to next(), which can be used for the next output.

function* gen(x) { 
    var y = 2 * (yield (x + 1)); 
    var z = yield (y / 3); 
    return (x + y + z); 
}

var g = gen(5); 
g.next() // { value:6, done:false } 
g.next(15) // { value:10, done:false } 
g.next(20) // { value:55, done:true }
Copy the code
  • Primary operationnextMethod, the system automatically ignores the parameter and returnsx+1The value of 6;
  • Second callnextMethod will be last timeyieldThe expression is set to 15, so y equals 30, and returnsy / 3The value of 10;
  • Third callnextMethod will be last timeyieldThe expression is set to 20, so z is equal to 20, x is equal to 5, y is equal to 30, so the return statement is equal to 55.

You can see that this parameter is treated as the return value of the previous yield statement. As a result of… Of is a looping method for iterating over an iterable, so it also applies to the traverser object returned by our generator. There is no need to call next. Similarly, methods of the same nature can iterate over the value of the traverser object, such as the extension operator array.from ().

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

var g = gen(); 
for (let item of g) { 
    console.log(item) / / 1, 2, 3
}
[...g]  / / [1, 2, 3]
Array.from(g)  / / [1, 2, 3]
Copy the code

return()

Whenever return() is called, the done value of the returned result is immediately set to true, terminating the traversal of the generator function, regardless of the number of yield pairs in the generator. If the return() function does not transmit parameters, value is undipay, and after the parameters are transferred, value is the transferred parameter.

function* generator() {
    yield 1
    yield 2
    yield 3
    yield 4
}
let g = generator()
console.log(g.next()); // {value: 1, done: false}
console.log(g.return()); // {value: undefined, done: true}
console.log(g.next(5)); // {value: undefined, done: true}
console.log(g.return(new Date())) // {value: Mon Sep 13 2021 23:37:57 GMT+0800, done: true}
console.log(g.next()); // {value: undefined, done: true}
Copy the code

throw()

After the throw() function is called, the error can be caught by a try/catch inside the Generator. The done value of the returned result is immediately set to true, ending the traversal of the Generator function. If the throw() function does not pass parameters, the captured exceptions are undipay, and after the parameters are transferred, the captured exceptions are the transferred parameters. Error() is usually instantiated in conjunction with this

function* gen() {
    try {
        yield 1;
        yield 2;
        yield 3;
        yield 4;
    } catch (e) {
        console.log("Generator internal error", e); }}var g = gen();
console.log(g.next()); // {value: 1, done: false}
console.log(g.next()); // {value: 2, done: false}
try {
    g.throw(new Error("Something went wrong")); // Generator internal Error: Something went wrong
    g.throw(new Error("oh no")); 
} catch (error) {
    console.log('External error', error); // External Error: oh no
}
console.log(g.next());  // {value: undefined, done: true}
Copy the code

The traverser object throws two errors, the first of which is caught internally by the Generator. Second, since the catch inside the function body has already been executed and no longer catches the error, the Generator body is thrown and caught by the catch outside the function body. Executing the next() method after that does not traverse further down.

How is the Generator instantiated internally

In fact, whether we declare an array, string, method, is actually generated by instantiating the corresponding constructor.

But that’s not a generalization, there are differences, and we’ll talk a little bit about what happens when you define base data types, the difference between new Function and Function declaration, and how base and reference data types are stored in memory. How are we instantiating the function here to generate the generator? If GeneratorFunction is printed on the console, there is no corresponding constructor, indicating that it is not on the window. So use the method of obtaining the prototype object, to find the constructor on his prototype object.

console.dir(Object.getPrototypeOf(function* (){}).constructor)
/ / print
1.  GeneratorFunction()
1.  1.  arguments: [Exception: TypeError: 'caller'.'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at Function.invokeGetter (<anonymous>:2:14)]
    1.  caller: [Exception: TypeError: 'caller'.'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at Function.invokeGetter (<anonymous>:2:14)]
    1.  length: 1
    1.  name: "GeneratorFunction"
    1.  prototype: GeneratorFunction
    1.  1.  arguments: (...).1.  caller: (...)
        1.  constructor: ƒ GeneratorFunction()
        1.  prototypeGenerator {constructorGeneratorFunction.next: ƒ.return: ƒ.throw: ƒ.Symbol(Symbol.toStringTag): "Generator1 "}.Symbol(Symbol.toStringTag): "GeneratorFunction"1.__proto__: ƒ ()
    1.  __proto__: ƒ Function()
    1.  [[Scopes]] :Scopes[0] // Let's verify that the constructor we found is correctgeneratorConstructor offunction* gen() {
    yield 1
}
console.log(gen instanceof Object.getPrototypeOf(function* (){}).constructor) / / true yes
Copy the code

If object.getPrototypeof (function*(){}).constructor is found as a constructor for the generator, then in turn we should be able to generate the generator by instantiating the constructor.

var GeneratorFunction = Object.getPrototypeOf(function* () { }).constructor;
var g = new GeneratorFunction("num"."yield num * 2");
console.log(g);  ƒ* anonymous(num) {yield num * 2}
var iterator = g(10);
console.log(iterator.next().value); / / 20
console.log(iterator); 
/*__proto__: Generator * [[GeneratorLocation]] * [[GeneratorStatus]]: "suspended" * [[GeneratorFunction]]: ƒ* anonymous(num) * [[GeneratorReceiver]]: Window * [[Scopes]]: Scopes[2] */GeneratorStatus has two values"suspended"and"closed"Corresponding donetrueandfalse

Copy the code

As it turns out, you can generate a Generator function object by instantiating GeneratorFunction.

Add: yield* expression

If we want to invoke another generator function within a generator function, we can use yield* to do so.

function* anotherGenerator(i) {
    yield i + 1;
    yield i + 2;
    yield i + 3;
}

function* generator(i) {
    yield i;
    yield* anotherGenerator(i); // Transfer of executive authority
    yield i + 10;
}

var gen = generator(10);

console.log(gen.next().value); / / 10
console.log(gen.next().value); / / 11
console.log(gen.next().value); / / 12
console.log(gen.next().value); / / 13
console.log(gen.next().value); / / 20
Copy the code

The yield* expression indicates that yield returns an traverser object to be used inside a Generator function.

The Generator function concludes with a summary:

  1. Define usingfunction*As a function declaration.
  2. callGeneratorFunction, returns aIterator objectNot the result of execution, internallyGeneratorStatusThere aresuspended.closedTwo status values, corresponding to whether completed or not.
  3. There can be more than one insideyieldTo do something like a normal functionreturnThe function.
  4. It can be provided through the APInext()Method, or statement for an iteration loopfor... of.Array.from(), expand the operator.forIterator objectTo output eachyieldAfter the values of thenext()You can also pass parameters as the previous oneyieldThe value participates in the next operation. The API andreturn().throw()
  5. GeneratorFunctiongeneratorConstructor, but not mounted inwindowOn.
  6. yield *Expressions can be in aGeneratorFunction returns another oneGeneratorFunction of theTraverser object.

If this article can help or inspire you, it will be my pleasure