I. Basic cognition of Generator

The basic principle is to pause at yield and continue to the next yield… That’s how we know we’re done.

Step () is an auxiliary function that controls iterators instead of manual next().

var a = 1;
var b = 2;
function* foo() {
  a++;
  yield;
  b = b * a;
  a = (yield b) + 3;
}
function* bar() {
  b--;
  yield;
  a = (yield 8) + b;
  b = a * (yield 2);
}
function step(gen) {
  var it = gen();
  var last;
  return function() {// Yield whatever it is, yield it back the same way next time! last = it.next(last).value; }; } a = 1; b = 2; var s1 = step(foo); var s2 = step(bar);Copy the code

There is a quantitative mismatch between the yield and next() calls, which means that to complete a generator function, the next() call always has one more yield


Why is there a mismatch?

Because the first next(..) Always start a generator and run to the first yield. However, it was the second next(..) The call completes the first paused yield expression and the third next(..). The call completes the second yield, and so on.


So in the code above, foo has two yields and bar has three yields so we’re going to run s1() three times and S2 () four times and we’re going to look at the output of each step on the console and analyze it step by step



At this point, you should have a general understanding of the basic working principles of the Generator.

If you want to get a little more understanding, feel free to switch the order of s1 and S2, three s1s and four S2s, as well as understanding how multiple generators can run concurrently in a shared scope.


Asynchronous iterator generator

In this section, we’ll look at the issues between generators and asynchronous programming, most directly network requests

letdata = ajax(url); // Ajax is supposed to be a wrapped web request method, not the native guy console.log(data)Copy the code

This code, I think we all know that it does not work properly, so DATA is underpay

Ajax is an asynchronous operation. Instead of stopping and waiting for data to be assigned to data, ajax simply executes the next line console.log(data) after the request is made.

Now that we know that the core of the problem is “not stopping” and that the generator happens to have “yield” stops, are the two just in tune

Look at a piece of code

function foo() {
  ajax(url, (err, data) => {
    if(err) {// Throw an error to *main() it.throw(err); }else{// Restore *main() it.next(data); }}); }function* main() {
  try {
    letdata = yield foo(); console.log(data); } catch (err) { console.error(err); }}Copy the code

This code uses generators, which do the same thing as the previous code, longer and more complex, but actually better, for more on this


First, the core difference between the two pieces of code is the use of yield in the generator


On yield Foo (), foo() is called, and no value is returned, so an Ajax request is made. Although I am still yield under25, I therefore pay, because the code therefore does not rely on the yield values to do anything. I am, therefore, underpaying

Yield is not used here in the sense of messaging, but only for process control to implement pause/block. In reality, it will still have messaging, but only one-way messaging after the generator is up and running.

So, the generator pauses at yield, essentially asking the question: “What value should I return to assign to the variable data?” Who’s going to answer that question?


Looking at Foo, if the Ajax request succeeds, the call to IT.next (data) will recover the generator with the response data, meaning that our paused yield expression received the value directly. This value is then assigned to the local variable data as the generator code continues to run


There’s code inside the generator that looks completely synchronous (except for the yield keyword itself), but lurking behind the scenes is code in foo(..) The inner run can be completely asynchronous


This section is important to understand the core of the connection between generators and asynchronous programming


Generator+Promise handle concurrent flow and optimization

Let’s move on to something superior. You can’t just stay theoretical

Request is a hypothetical, promise-based implementation

Run is also a method that assumes a wrapped Promise chain that drives repeated iterations

function *foo() {
  let r1 = yield request(url1);
  let r2 = yield request(url2);

  let r3 = yield request(`${url3}/${r1}/${r2}`);

  console.log(r3)
}
run(foo)
Copy the code

In this code, R3 is dependent on R1 and R2, and R1 and R2 are serial, but these two requests are relatively independent, so should we consider concurrent execution?

However, yield is only a single pause point in code, and it is not possible to pause at both points at the same time

Try it this way

function *foo() {
  let p1 = request(url1);
  let p2 = request(url2);

  let r1 = yield p1;
  let r2 = yield p2;

  let r3 = yield request(`${url3}/${r1}/${r2}`);

  console.log(r3)
}
run(foo)
Copy the code

Looking at the yield position, P1 and P2 are promises for Ajax requests that are executed concurrently. It doesn’t matter which one completes first, because the promise will remain in the resolution state for as long as necessary


The next two yield statements are then used to wait and get the promise’s resolution (r1 and R2, respectively).

If P1 is resolved first, yield P1 will resume execution and wait for Yield P2 to resume.

If P2 talks first, it will patiently hold its resolution value and wait for the request, but yield P1 will wait until P1 decides.

In either case, P1 and P2 are executed concurrently, regardless of the order in which they are completed, and then r3 = yield request.. Ajax requests.


This process control model and promise.all ([..] ) tools implement the same gate pattern

function *foo() {
  let rs = yield Promise.all([
    request(url1),
    request(url2)
  ]);

  let r1 = rs[0];
  let r2 = rs[1];

  let r3 = yield request(`${url3}/${r1}/${r2}`);
  console.log(r3)
}
run(foo)
Copy the code


Abstract asynchronous Promise flow and simplify generator

So far, promises have been exposed directly inside the generator, but the point of generator asynchrony is to create simple, sequential, synchronous-looking code that hides the details of asynchrony as much as possible.


Think about hiding all the extra information, especially the Promise code that looks complicated?

function bar(url1, url2) {
  return Promise.all([request(url1), request(url2)]);
}

function* foo() {// hide bar(..) Internal promise-based concurrency detailslet rs = yield bar(url1, url2);
  let r1 = rs[0];
  let r2 = rs[1];

  let r3 = yield request(`${url3}/${r1}/${r2}`);
  console.log(r3);
}
run(foo);
Copy the code

The implementation details of Promise are encapsulated in BAR. The requirement of BAR is to give us rs results, and we do not need what is used to realize the bottom of the relationship

Asynchrony, in effect, treats promises as an implementation detail.

In actual production, a series of asynchronous process control may be the following implementation

function bar() {
  Promise.all([
    bax(...).then(...),
    Promise.race([...])
  ])
  .then(...)
}
Copy the code

This code can be so complex that if you put the implementation directly inside the generator, there’s almost no reason to use the generator


Keep this in mind: Create simple, sequential, synchronous-looking code that hides asynchronous details as much as possible.


The latter

Thank you for your patience to see here, hope to gain!

If you are not very busy, please click star⭐ [Github blog portal], which is a great encouragement to the author.

I like to keep records during the learning process and share my accumulation and thinking on the road of front-end. I hope to communicate and make progress with you. For more articles, please refer to [amandakelake’s Github blog].