JavaScript Basics: generator

The generator feature of ES6, which is available in most other languages, can be used to implement collaborative processes, but I feel that Promise is convenient for most scenarios where asynchronous logic is written. Link to the original text: juejin.cn/post/6928660813683097613 , author: Bodhi listening

summary

Once a function in JavaScript starts running, it will not be interrupted until it ends. However, a new function form Generator is introduced in ES6. This function does not guarantee the execution to the end like ordinary functions, but has the ability to pause and resume code execution in the function block. The form of Generator is just to add a * before the name of ordinary function to indicate that this is a Generator. There are two main features: 1. There is an asterisk between the function keyword and the function name; 2. The keyword yield expression is used inside the function body to define different internal states. The use of Generator is the same as that of ordinary functions, except that calling the Generator function will produce a iterator Therefore, you can call the next function of the iterator to start or resume execution.

// This is a Generator

function *genFunc () {
  yield 'generator';
  yield 'hello';
  yield 'word';
  return '!';
}

const it = genFunc();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

As follows, the implementation result is iterator Generated content:

yield keyword

The generator is the pause point by using the yield keyword. In other words, the generator function will execute normally before encountering the yield keyword. When encountering the keyword, the execution will be suspended, and the scope state of the function will be retained until the next() function is called to resume execution. At this time, the yield keyword is a bit like an intermediate return statement of a function. The value generated by it will appear in the object returned by next (). The yield keyword can only be used inside the generator function, and errors will be thrown elsewhere.

// Common functions use yield

function func () {
  yield 'test yield Use of keywords';
}

yield * expression

How to call another generator function in the generator function requires theoretically traversing another generator function in the function body.

function *gen1 () {
  yield 'gen1: hellow';
  yield 'gen1: word';
  return 'gen1';
}

function *gen2 () {
  for (const genValue of gen1()) {
    console.log(genValue);
  }
  yield 'gen2: hellow';
  yield 'gen2: word';
  return 'gen2';
}

const it = gen2();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

ES6 provides a yield * expression, that is, a yield delegation to solve such problems. yield* ... You need an iterable object, and then it will call iterable's iterator and delegate its generator control to this iterator until it runs out.

function *gen3 () {
  const genValue = yield* gen1();
  console.log('genValue:', genValue);
  yield 'gen3: hellow';
  yield 'gen3: word';
  return 'gen3';
}

const it = gen3();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

Therefore, you can use yield * expressions to implement recursive operations.

function *genMult (x) {
  if (x < 3) {
      x = yield* genMult(x + 1);
  }
  return x * 2;
}

const it = genMult(1);
it.next(); // {value: 24, done: true}

yield input and output

Yield can be used either as an intermediate return statement of a function or as an intermediate parameter of a function. The yield expression itself does not return a value, or it always returns undefined. The next method can take a parameter, which will be regarded as the return value of the previous yield expression.

function *sum (x) {
  const y = yield;
  const z = yield;
  return x + y + z;
}

const it = sum(1);
console.log(it.next());
console.log(it.next(2));
console.log(it.next(3));

The value of the first call to next will not be used, because the first call is to start the execution of the generator function.

Early termination generator

Similar to iterators, you can use return and throw to end the state of a tumbler in advance, and generators can also use return and throw methods to terminate generators in advance. The return method will force the generator to close. The value passed in by the return method is the value of terminating the generator. The closed state cannot be restored after switching. If you call the next method again, done: true will always be maintained.

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

const it = returnFunc();
console.log(it.next());
console.log(it.return(4));
console.log(it.next());

The return method not only closes the generator, but also includes a finally clause in the generator function. It can also perform love tasks, such as resource release, state reset, etc. That is, if there is a try inside the generator function If a finally code block is being executed and a try code block is being executed, the return() method will immediately enter the finally code block. After execution, the whole function will not end.

function *returnFunc () {
  try {
    yield 1;
    yield 2;
  } finally {
    yield 3;
    yield 5;
  }
}

const it = returnFunc();
console.log(it.next());
console.log(it.return(4));
console.log(it.next());
console.log(it.next());

Note that after calling the return() method, the finally code block will be executed immediately without executing the remaining code in the try. Then, wait until the finally code block is executed, and then return the value specified by the return() method.

The throw method will inject a provided error into the generator object during pause, which is equivalent to a throw... At the pause point. If there is a catch module inside the generator to handle errors, the generator will not close and execution can be resumed.

function *throwFunc () {
  try {
    yield 1;
    yield 2;
  } catch (e) {
      console.error(e);
    yield 3;
    yield 5;
  }
}

const it = throwFunc();
console.log(it.next());
console.log(it.throw(new Error('throw error')));
console.log(it.next());

Tags: Javascript

Posted by daniminas on Tue, 19 Apr 2022 08:36:51 +0930