ES6 generator, a seemingly synchronous asynchronous flow control expression style

This article is shared from the Huawei Cloud Community " March Reading Week·JavaScript You Don't Know | ES6 Generator, a seemingly synchronous asynchronous process control expression style ", author: Ye Yiyi.

Builder

break full run

There is an assumption that JavaScript developers almost universally rely on in their code: once a function starts executing, it will run to the end, and no other code can interrupt it and insert it in between.

ES6 introduces a new function type that does not conform to this run-to-end feature. This new type of function is called a generator.

var x = 1;

function foo() {
  x++;
  bar(); // <-- this line runs between the x++ and console.log(x) statements
  console.log('x:', x);
}

function bar() {
  x++;
}

foo(); // x: 3

What happens if bar() isn't there? Obviously the result will be 2, not 3. The final result is 3, so bar() will run between x++ and console.log(x).

But JavaScript is not preemptive, nor is it multi-threaded (yet). However, it would still be possible to implement such interruptions in a cooperative manner (concurrency) if foo() itself could somehow indicate a pause at this point in the code.

Here is the ES6 code to implement cooperative concurrency:

var x = 1;

function* foo() {
  x++;
  yield; // Pause!
  console.log('x:', x);
}

function bar() {
  x++;
}
//Construct an iterator it to control this generator
var it = foo();

//Start foo() here!
it.next();
console.log('x:', x); // 2
bar();
console.log('x:', x); // 3
it.next(); // x: 3
  • The it = foo() operation does not execute the generator *foo(), but only constructs an iterator (iterator), which controls its execution.
  • *foo() pauses at the yield statement, at which point the first it.next() call ends. At this point *foo() is still running and active, but in a suspended state.
  • The final it.next() call resumes execution of the generator *foo() from where it was paused and runs the console.log(..) statement, which uses the current value of x, 3.

A generator is a special type of function that can be started and stopped one or more times, but does not necessarily have to be completed.

input and output

A generator function is a special function that still has some basic properties of functions. For example, it can still accept parameters (i.e. input) and return values ​​(i.e. output).

function* foo(x, y) {
  return x * y;
}

var it = foo(6, 7);
var res = it.next();

res.value; // 42

Pass the actual parameters 6 and 7 to *foo(..) as parameters x and y respectively. *foo(..) returns 42 to the calling code.

multiple iterators

Every time you build an iterator, you actually implicitly build an instance of the generator. It is the generator instance that is controlled through this iterator.

Multiple instances of the same generator can run simultaneously, and they can even interact with each other:

function* foo() {
  var x = yield 2;
  z++;
  var y = yield x * z;
  console.log(x, y, z);
}

var z = 1;

var it1 = foo();
var it2 = foo();

var val1 = it1.next().value; // 2 <-- yield 2
var val2 = it2.next().value; // 2 <-- yield 2

val1 = it1.next(val2 * 10).value; // 40   <-- x:20,  z:2
val2 = it2.next(val1 * 5).value; // 600  <-- x:200, z:3

it1.next(val2 / 2); // y:300
// 20300 3
it2.next(val1 / 4); // y:10
// 200 10 3

Briefly summarize the execution process:

(1) Two instances of *foo() are started at the same time, and the two next() get the value 2 from the yield 2 statement respectively.

(2) val2 * 10, which is 2 * 10, is sent to the first generator instance it1, so x gets the value 20. z increases from 1 to 2, then 20 * 2 is emitted via yield, setting val1 to 40.

(3) val1 * 5, which is 40 * 5, is sent to the second generator instance it2, so x gets the value 200. z is incremented from 2 to 3 again, then 200 * 3 is emitted via yield, setting val2 to 600.

(4) val2 / 2, which is 600 / 2, is sent to the first generator instance it1, so y gets the value 300, and then the values ​​of xyz are printed out as 20300 3 respectively.

(5) val1 / 4, which is 40 / 4, is sent to the second generator instance it2, so y gets the value 10, and then the values ​​of xyz are printed out as 200 10 3 respectively.

Generator produces value

Producers and Iterators

Suppose you want to generate a sequence of values, each of which has a specific relationship to the previous one. Achieving this requires a stateful producer that can remember the last value it produced.

An iterator is a well-defined interface for getting a sequence of values ​​from a producer step by step. The interface of JavaScript iterator is to call next() every time you want to get the next value from the producer.

The standard iterator interface can be implemented for numeric sequence generators:

var something = (function () {
  var nextValid;

  return {
    // for..of loop required
    [Symbol.iterator]: function () {
      return this;
    },

    // Standard iterator interface method
    next: function () {
      if (nextVal === undefined) {
        nextVal = 1;
      } else {
        nextVal = 3 * nextVal + 6;
      }

      return { done: false, value: nextVal };
    },
  };
})();

something.next().value; // 1
something.next().value; // 9
something.next().value; // 33
something.next().value; // 105

The next() call returns an object. This object has two properties: done is a boolean value that identifies the completion status of the iterator; value places the iteration value.

iterable

iterable is an object that contains an iterator that can be iterated over its values.

Starting with ES6, the way to extract an iterator from an iterable is that the iterable must support a function whose name is the specialized ES6 symbol value Symbol.iterator. When this function is called, it returns an iterator. Usually each call returns a new iterator, although this is not required.

var a = [1, 3, 5, 7, 9];

for (var v of a) {
  console.log(v);
}
// 1 3 5 7 9

a in the above code snippet is an iterable. The for..of loop automatically calls its Symbol.iterator function to build an iterator.

for (var v of something) {
  ..
}

The for..of loop expects something to be an iterable, so it looks for and calls its Symbol.iterator function.

generator iterator

The generator can be regarded as a producer of values. We extract one value at a time through the next() call of the iterator interface.

Generators themselves are not iterable. When you execute a generator, you get an iterator:

function *foo(){ .. }

var it = foo();

The previous something infinite number sequence producer can be implemented through a generator, similar to this:

function* something() {
  var nextValid;

  while (true) {
    if (nextVal === undefined) {
      nextVal = 1;
    } else {
      nextVal = 3 * nextVal + 6;
    }

    yield nextVal;
  }
}

Because the generator pauses at each yield, the state (scope) of function *something() is maintained, meaning that no closure is needed to maintain variable state between calls.

Asynchronous iterator generator

function foo(x, y) {
  ajax('http://some.url.1/? x=' + x + '&y=' + y, function (err, data) {
    if (err) {
      // Throw an error to *main()
      it.throw(err);
    } else {
      //Restore *main() with received data
      it.next(data);
    }
  });
}

function* main() {
  try {
    var text = yield foo(11, 31);
    console.log(text);
  } catch (err) {
    console.error(err);
  }
}

var it = main();

//Start here!
it.next();

In yield foo(11,31), foo(11,31) is first called, which returns no value (i.e. returns undefined), so a call is made to request the data, but what is actually done afterwards is yield undefined.

Yield is not used here in the sense of message passing, but only used for flow control to implement pausing/blocking. In fact, there will still be message passing, but it will only be a one-way message passing after the generator resumes operation.

Take a look at foo(..). If this Ajax request is successful, we call:

it.next(data);

This resumes the generator with the response data, meaning that the paused yield expression received this value directly. This value is then assigned to the local variable text as the generator code continues to run.

Summarize

Let’s summarize the main contents of this article:

  • Generator is a new function type in ES6. It does not always run to the end like ordinary functions. Instead, a generator can be paused while running (preserving its state entirely) and resumed from where it was paused at a later time.
  • The pair of yield/next(..) is not only a control mechanism, but also a two-way message passing mechanism. yield .. The expression essentially pauses and waits for a value, and the subsequent next(..) call returns a value (or implicitly undefined) to the paused yield expression.
  • The key advantage of generators when it comes to asynchronous control flow is that the code inside the generator is a sequence of steps expressing a task in a naturally synchronous/sequential way. The trick is to hide the possible asynchrony behind the yield keyword and move the asynchrony to the part of the code that controls the iterator of the generator.
  • Generators maintain a sequential, synchronous, blocking code pattern for asynchronous code, which allows the brain to follow the code more naturally, solving one of the two key flaws of callback-based asynchrony.

 

Click to follow and learn about Huawei Cloud’s new technologies as soon as possible~

 

The first major version update of JetBrains 2024 (2024.1) is open source. Even Microsoft plans to pay for it. Why is it still being criticized for open source? [Recovered] Tencent Cloud backend crashed: A large number of service errors and no data after logging in to the console. Germany also needs to be "independently controllable". The state government migrated 30,000 PCs from Windows to Linux deepin-IDE and finally achieved bootstrapping! Visual Studio Code 1.88 is released. Good guy, Tencent has really turned Switch into a "thinking learning machine". RustDesk remote desktop starts and reconstructs the Web client. WeChat's open source terminal database based on SQLite, WCDB, has received a major upgrade.
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4526289/blog/11051652