Note: This is a compilation of notes from Understanding ECMAScript 6 on Github, which also includes the code samples. You can read the book when you have time. Although English, but easy to understand, highly recommended.

Previously: In my last article do you know why there is a Generator, I introduced the reason for the Generator. At the time, a partner pointed out that “The Generator is designed to simulate the hibernation mechanism of multithreading” and “the Generator runs lazily”. At that time I said there would be an introduction in the advanced chapter, so here is a good introduction.

Abstract: The focus here is on how to communicate with the generator. First, we can use next() to pass parameters, and second, we can also use throw(). The difference is that it throws errors into the generator. The second is the execution order within the generator when there are yield assignment statements. Finally, how to write asynchronously in synchronous mode (possibly co oh).

The original address

If you are not familiar with generator, take a look here

1. The refs

You can pass a parameter to the next, and the yield receives the parameter in a generator, as shown in the following example:

function *createIterator() {
  let first = yield 1;
  letsecond = yield first + 2; // 4 + 2 yield second + 3; // 5 + 3}let iterator = createIterator();   
console.log(iterator.next());      // "{ value: 1, done: false }"
console.log(iterator.next(4));     // "{ value: 6, done: false }"
console.log(iterator.next(5));     // "{ value: 8, done: false }"
console.log(iterator.next());      // "{ value: undefined, done: true }"
Copy the code

Execute the process

To make the above execution clear, use this diagram:

The same color is executed in the same iteration, from light to dark, indicating the sequence of iterations. Such as:

  1. First callnext(), the implementation ofyield 1To stop, return{ value: 1, done: false }.Pay attention to, which is the assignment statementlet fisrt = ...Failure to execute;
  2. Second callnext(4), first put the parameters4Pass in last timeyield, can be understood as:
letfirst = yield 1; = >let first = 4;
Copy the code

The execution picks up where it left off, that is, the assignment statement is executed first

let first = 4
Copy the code

It then executes until the next yield, i.e

yield first + 2  // 4 + 2
Copy the code

{value: 6, done: false}

The next iteration follows this principle until the iteration is complete.

In other words, the iterator generated by the next parameters, generator, provides a bridge to the external environment. Combining with iterator’s ability to pause, it can do some interesting things, such as write callback synchronously, as described below.

2. ToiteratorThrow in the wrong

function *createIterator() {
  let first = yield 1;
  letsecond = yield first + 2; // yield 4 + 2, throw error yield second + 3; // will not be executed}let iterator = createIterator();
	
console.log(iterator.next());  // {value: 1, done: false}
console.log(iterator.next(4)); // {value: 6, done: false}
console.log(iterator.throw(new Error("Boom"))); // Error thrown in generatorCopy the code

According to the execution mechanism described above, the execution flow of this example can be represented by this diagram:

On the third iteration, we call iterator.throw(new Error(“Boom”)), which throws an Error into the iterator with an Error message.

We can modify createIterator as follows:

function* createIterator() {
  let first = yield 1;
  let second;
  try {
    second = yield first + 2;
  } catch (ex) {
    second = 6;
  }
  yield second + 3;
}
let iterator = createIterator();                 
console.log(iterator.next());                   // "{ value: 1, done: false }"
console.log(iterator.next(4));                  // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // "{ value: 9, done: false }"
console.log(iterator.next());                   // "{ value: undefined, done: true }"
Copy the code

The execution process is explained as follows:

  1. The first two calls to Next are the same as the analysis in the execution mechanism above.

  2. The third call to iterator.throw(new Error(“Boom”) throws an Error at the generator, which is received internally at the last stop, yield first + 2, and throws an Error. But caught, so continue to the next pause:

    yield second + 3; / / 6 + 3Copy the code

    {value: 9, done: false}

  3. Continue with the other iterations, which are the same as above.

Summary: As you can see here, both next() and throw() allow iterator to continue, except that the latter throws an error to continue execution. But what happens in the generator after that depends on how the code is written.

3. GeneratorIn thereturnstatements

A return statement is functionally the same as a return of a normal function.

function* createIterator() {
  yield 1;
  return;
  yield 2;
  yield 3;
}
let iterator = createIterator();
console.log(iterator.next());  // "{ value: 1, done: false }"
console.log(iterator.next());  // "{ value: undefined, done: true }"
Copy the code

The return above causes subsequent yields to be ignored, so iterates twice and dies.

However, if there is a value after the return, it will be counted in the result of the iteration:

function* createIterator() {
  yield 1;
  return 42;
}
let iterator = createIterator();
console.log(iterator.next());  // "{ value: 1, done: false }"
console.log(iterator.next());  // "{ value: 42, done: true }"
console.log(iterator.next());  // "{ value: undefined, done: true }"
Copy the code

{value: 42, done: true} is the result of the iterator.

However, this return value can only be used once, so the third time next returns {value: undefined, done: true}.

Special note: The expansion operator… If done is true in the result of the iteration, it will stop executing immediately, even if the value after return is ignored. In the example above, use for-of and… Perform:

function* createIterator() {
  yield 1;
  return 42;
}

let iterator = createIterator();

for(letitem of iterator) { console.log(item); } / / 1letanotherIterator = createIterator(); Console. log([...anotherIterator]) // [1] // Guess what the result of [...iterator] isCopy the code

4. Commissioned by the Generator

Generator A delegate to Generator B for generator B to execute:

function* createNumberIterator() {
  yield 1;
  yield 2;
}
function* createColorIterator() {
  yield "red";
  yield "green";
}
function* createCombinedIterator() {
  yield* createNumberIterator();
  yield* createColorIterator();
  yield true;
}

var iterator = createCombinedIterator();

console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "red", done: false }"
console.log(iterator.next()); // "{ value: "green", done: false }"
console.log(iterator.next()); // "{ value: true, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
Copy the code

As you can see, the syntax of a delegate is to yield* on one generator for the execution of another.

By delegating different generators together and using the return value of return, we can communicate within the generator, which gives more imagination:

function* createNumberIterator() {
  yield 1;
  yield 2;
  return 3;
}
function* createRepeatingIterator(count) {
  for (let i = 0; i < count; i++) {
    yield "repeat"; }}function* createCombinedIterator() {
  let result = yield* createNumberIterator();
  yield* createRepeatingIterator(result);
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
Copy the code

As above, the return value of createNumberIterator 3 is passed into createRepeatingIterator, which looks like this if you break it apart:

function* createNumberIterator() {
  yield 1;
  yield 2;
  return 3;
}

function* createRepeatingIterator(count) {
  for (let i = 0; i < count; i++) {
    yield "repeat"; }}function* createCombinedIterator() {
  let result = yield* createNumberIterator();
  yield result;
  yield* createRepeatingIterator(result);
}

var iterator = createCombinedIterator();
console.log(iterator.next());  // "{ value: 1, done: false }"
console.log(iterator.next());  // "{ value: 2, done: false }"
console.log(iterator.next());  // "{ value: 3, done: false }"
console.log(iterator.next());  // "{ value: "repeat", done: false }"
console.log(iterator.next());  // "{ value: "repeat", done: false }"
console.log(iterator.next());  // "{ value: "repeat", done: false }"
console.log(iterator.next());  // "{ value: undefined, done: true }"
Copy the code

Note: Since yield * is followed by generator, generator is iterable. That is, yield * can be followed directly by iterable, such as strings. Such as:

  let g = function *() {
    yield *['a'.'b'.'c']}for(let item of g()) {
    console.log(item);
  }
  
  // a
  // b
  // c
Copy the code

5. GenaratorWith an asynchronous

About the characteristics of asynchronous JS, here expand said. Simply put, it makes single-threaded languages like JS more powerful; However, in asynchrony cases where there are dependencies between asynchrons, it is easy to write callback hell, but difficult to maintain:

Reasonable use of Genarator can be used to write synchronous, write asynchronous.

As you already know from the introduction, genarator returns iterator, requiring a manual call to next, which is cumbersome. It would be nice to encapsulate something and let the iterator execute on its own:

  1. Preparation, implementation of automatic execution of generator functions

    run(function* () {
      let value = yield 1;
      console.log(value);
      value = yield value + 3;
      console.log(value);
    });
    Copy the code

    To make it run by itself, run needs to:

    1. performgeneratorAnd getiterator;
    2. calliterator.next();
    3. Make the result of the previous step the next timeiterator.next(lastResult)Parameter, continue iterating;
    4. Repeat 3 until the iteration is complete.

    The implementation is as follows:

    functionRun (taskDef) {// Create and save the iterator for later uselet task = taskDef();
      
      letresult = task.next(); // Execute 'next' recursivelyfunction step() {// If notif(! result.done) { result = task.next(result.value); step(); }} // start processing step(); }Copy the code
  2. Achieve the goal of writing asynchronously in a synchronous manner

    Let’s make the following code work:

    const asyncWork = new Promise((resolve, reject) => {
      setTimeout(() => resolve(5), 500)
    })
    
    
    run(function* () {
      let value = yield asyncWork;
      console.log(value)
      value = yield value + 3;
      console.log(value)
    });
    
    Copy the code

    The difference between this and the previous example is that the yield may be a promise, so we can add a judgment:

    if (result.value && typeof result.value.then === 'function') { result.value.then(d => { result = task.next(d) ... })}Copy the code

    If it is a promise, execute the then function and pass the result back to the next iteration, next(d). The complete sample code is as follows:

    functionRun (taskDef) {// Create and save the iterator for later uselet task = taskDef();
      
      letresult = task.next(); // Execute 'next' recursivelyfunction step() {// If notif(! result.done) {if (result.value && typeof result.value.then === 'function') { result.value.then(d => { result = task.next(d) step(); })}else{ result = task.next(result.value); step(); }}} // start processing step(); }Copy the code

    Let’s go back to this:

    run(function* () {
      let value = yield asyncWork;
      console.log(value)
      value = yield value + 3;
      console.log(value)
    });
    Copy the code

    Although the second yield is dependent on the previous yield, instead of writing it as a callback, it looks just as straightforward as synchronization!

conclusion

An iterator generated by a generator can be passed to the generator from outside the function using next, and an error can be thrown in by a throw. They act as opening multiple communication Windows within the generator, making clear asynchrony possible. The powerful Redux-Saga is also implemented based on generators. Is there more to play? Everything is throwing bricks and mortar, I wonder if you have other ways to play?

If you are not sure about the origin of the Generator, you can also take a look here

Also, this post was originally posted on Github as part of a series about ES6. If you feel ok, help star, easy to find a job. Ah, looking for a job, really – is – tired!!