What is the Generator

Generator functions are an asynchronous programming solution provided by ES6 with completely different syntactic behavior from traditional functions. Generator functions can be understood in many ways. Syntactically, the Generator function is a state machine that encapsulates multiple internal states. Formally, a Generator function is an ordinary function, but has two characteristics. There is an asterisk between the function keyword and the function name. Second, inside the function body, yield expressions are used to define different internal states (yield means “output” in English).

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }
Copy the code

I need to add an Iterator Object here.

Iterator

The Iterator traverses like this.

(1) Create a pointer object that points to the starting position of the current data structure. That is, the traverser object is essentially a pointer object.

(2) The first call to the next method of the pointer object can point to the first member of the data structure.

(3) The next call to the pointer object points to the second member of the data structure.

(4) Keep calling the next method of the pointer object until it points to the end of the data structure. The next method returns an object representing information about the current data member. This object has two properties, value and done. The value property returns the member of the current position, and the done property is a Boolean value indicating whether the traversal is complete, that is, whether it is necessary to call the next method again.

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++]} :
        {done: true}; }}; };var it = makeIterator(['a'.'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
Copy the code

Some ES6 data structures have native Iterator interfaces (such as arrays) that can be used for… The of loop traverses. The reason is that these data structures have symbol. iterator attributes deployed natively, while others (such as objects) do not. Any data structure that deploys the symbol. iterator property is said to have deployed the traverser interface. Calling this interface returns a traverser object. The data structures with the Iterator interface are as follows:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • The arguments object for the function
  • The NodeList object
let arr = ['a'.'b'.'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
Copy the code

Parameters to the next method

The yield expression itself returns no value, or always returns undefined. The next method can take an argument that is treated as the return value of the previous yield expression. If the yield expression is used in another expression, it must be enclosed in parentheses. Yield expressions are used as function arguments or to the right of assignment expressions, without parentheses.

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

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);                                 / / with x = 5 refs
b.next() // { value:6, done:false } // yield (x + 1) x+1 = 5+1
b.next(12) // {value:8, done:false} // yield (x + 1) = 12
b.next(13) // {value:42, done:true} // yield (y / 3) = 13
Copy the code

for… Of circulation

The following code uses for… The of loop displays the values of the five yield expressions in turn. Note here that once the next method returns an object with the done attribute true, for… The of loop terminates and does not contain the return object, so the 6 returned by the return statement above is not included in the for… In the loop of.

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1, 2, 3, 4, 5
Copy the code

Note:

Next () corresponds to the yield expression, and if for… {value: undefined, done: true}, but next() for… Of will only display based on the remaining yield

var arr = [1The [[2.3].4], [5.6]].var flat = function* (a) {
  var length = a.length;
  for (var i = 0; i < length; i++) {
    var item = a[i];
    if (typeofitem ! = ='number') {
      yield* flat(item);
    } else {
      yielditem; }}};for (var f of flat(arr)) {
  console.log(f);
};
// 1 2 3 4 5 6
Copy the code

Next () in the for… After of

let arrs = flat(arr);
for (var f of arrs) {
  console.log(f);
}; 
console.log(arrs.next());
// 1 2 3 4 5 6 { value: undefined, done: true }
Copy the code

Next () in the for… Of the former

let arrs = flat(arr);
console.log(arrs.next());
for (var f of arrs) {
  console.log(f);
}; 
// { value:1, done: false } 2 3 4 5 6 
Copy the code

Fibonacci numbers

The Fibonacci sequence, also known as the Golden section sequence, was introduced by mathematician Leonardoda Fibonacci as an example of rabbit reproduction. It is also known as the Rabbit sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34… Mathematically, the Fibonacci sequence is defined recursively as follows: F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2) (n ≥ 2, n ∈ N*)

An array of

 function list(max){
	 let arr = [];
	 let a = 0;
	 let b = 1;
	 while(arr.length<max){
		 arr.push(a);
		 [a,b] = [b,a+b];
	 } 
	 return arr;
 }
 console.log(list(5));/ /,1,1,2,3 [0]
Copy the code

The generator function

 function* list(max){
	let a = 0;
	let b = 1;
	let n = 0;
	while(n<max){
	  yield a;
	  [a,b] = [b,a+b];
	  n++;
	};
	return;
  };
  var arr = list(5);
  for (let item of arr) {
  	console.log(item);
  }
// 0 1 1 2 3
Copy the code

thinking

What’s good about Generator functions? Back to basics: Functions are an asynchronous programming solution provided by ES6. Using the Fibonacci column example, both the normal constructor and the Generator function work, but the difference is that the normal function returns an array of >. The Generator function is executed from the top to the next, which is obvious with the next() method.

What is asynchrony

The concept of asynchrony is to execute step by step. The next event will be executed only after the last event has been executed. Compare this to the if condition, which determines whether the last event has been executed.

Implementation of asynchronous programming: callback functions

The JavaScript language’s implementation of asynchronous programming is the callback function. The so-called callback function is to write the second section of the task in a separate function, and wait until the task is executed again, directly call the function.

var fs = require('fs');
fs.readFile('./zf.js'.'utf-8'.function (err, data) {
  if (err) throw err;
  console.log('data',data);
});
Copy the code

In the code above, the third argument to the readFile function is the callback function, which is the second segment of the task. The callback will not be executed until the operating system returns the./zf.js file.

An interesting question is why Node conventions that the first argument to the callback must be the error object err (or null if there are no errors).

The reason is that the execution is divided into two parts, and by the end of the first part, the context in which the task is executed is over. Errors thrown after this point cannot be caught in the original context and can only be passed as arguments to the second paragraph.

Strong coupling of callback functions

var fs = require('fs');
fs.readFile(fileA, 'utf-8'.function (err, data) {
    fs.readFile(fileB, 'utf-8'.function (err, data) {
        / /...
    });
});
Copy the code

Take reading multiple files as an example. If one operation needs to be modified, its upper and lower callback functions may need to be modified. This situation is called “callback hell.”

Promise objects are created to solve this problem. It is not a new syntactic feature, but a new way of writing that allows you to change the nesting of callback functions to chain calls.

The fs-readfile-promise module returns a promise version of the readfile function. Promise provides then methods to load callback functions and catch methods to catch errors thrown during execution.

var readFile = require('fs-readfile-promise');

readFile(fileA)
.then(function (data) {
  console.log(data.toString());
})
.then(function () {
  return readFile(fileB);
})
.then(function (data) {
  console.log(data.toString());
})
.catch(function (err) {
  console.log(err);
});
Copy the code

The biggest problem with Promise

The biggest problem with promises is that the code is redundant. The original tasks are wrapped up by Promises, so that no matter what operation is performed, the original semantics become very unclear.

Redundant execution redundancy: for example, in the function of a certain program, the statement, in the returned parameters have no impact, but the execution of multiple times, is redundant execution, this redundancy is the CONSUMPTION of CPU, should be eliminated this kind of redundancy, should be commented out.

Code redundancy: Too many comments in the code, or variables or functions that are not used, can make the code less readable

The occurrence of Generator functions

ES6 provides an asynchronous programming solution with completely different syntactic behavior from traditional functions

coroutines

Traditional programming languages have long had solutions for asynchronous programming (actually multitasking). One of these is called a coroutine, which means that multiple threads work together to complete asynchronous tasks.

Coroutines are a little bit like functions and a little bit like threads. Its operation process is roughly as follows.

  • Step one, coroutine A starts executing.
  • In the second step, coroutine A is paused halfway through execution, and execution authority is transferred to coroutine B.
  • In the third step, the coroutine B returns execution authority.
  • Step 4, coroutine A resumes execution.

The coroutine A of the above process is an asynchronous task because it is executed in two (or more) segments.

function* asyncJob() {
  / /... Other code
  var f = yield readFile(fileA);
  / /... Other code
}
Copy the code

The asyncJob function in the above code is a coroutine whose secret is the yield command. It says execution at this point, execution will be handed over to some other coroutine. That is, the yield command is the boundary between the two phases of asynchrony.

The coroutine pauses at yield, returns to execution, and continues from where it was suspended. The biggest advantage of this is that the code is written very much like a synchronous operation, which is exactly the same without the yield command.

Data exchange and error handling of Generator functions

The Generator function can pause and resume execution, which is the fundamental reason it encapsulates asynchronous tasks. In addition, it has two features that make it a complete solution for asynchronous programming: data exchange and error handling inside and outside functions.

The value property of the next return value is the Generator function that outputs data; The next method can also accept arguments and input data into the body of the Generator function.

function* gen(x){
  try {
    yield;
  } catch (e){
    console.log('Internal capture',e); }}var g = gen(1);

g.next()
try {
  g.throw('a');
  g.throw('b');
} catch (e) {
  console.log('External capture', e);
}
// Internally capture a
// External capture b
Copy the code

In the code above, outside of the Generator function, an error thrown using a pointer object’s throw method can be thrown by a try… Catch code block capture. This means that there is a separation in time and space between the code that makes mistakes and the code that handles them, which is important for asynchronous programming.

Automatic management of the Generator

function run(fn) {
  var gen = fn();
  function next(data) {
	  console.log('Automatic cycle',data)
    var result = gen.next(data);
	console.log(result.value)
    if (result.done) return;
    next(result.value)
  };
  next();
};
function* f(){
	yield 1;
	yield 2;
	return 3;
}
console.log('Sync execution begins');
run(f);
console.log('End of execution')
Copy the code

async… await

The ES2017 standard introduces async functions to make asynchronous operations more convenient. What is async function? In short, it is the syntactic sugar of Generator functions.

function* f(){
	yield 1;
	yield 2;
	return 3;
}
Copy the code
async function f(){
	await 1;
	await 2;
        await 3;
};
f();
Copy the code

An async function simply replaces the asterisk (*) of a Generator function with async, replaces yield with await, and that’s it.

The improvements of async over Generator are shown in the following four aspects:

  1. Built-in actuator
  2. Better semantics
  3. Wider applicability
  4. The return value is Promise

(1) Built-in actuators. Generator functions must be executed by an executor (wrapped in automatic management mode), whereas async functions come with an executor. In other words, async functions are executed exactly like normal functions, with only one line.

(2) Better semantics. Async and await are semantic clearer than asterisks and yield. Async means that there is an asynchronous operation in a function, and await means that the following expression needs to wait for the result.

(3) wider applicability. The await command of an async function can be followed by a Promise object and a value of the original type (numeric, string, and Boolean, but then automatically converted to an immediately resolved Promise object).

(4) Return the Promise. Async functions return a Promise object, which is much more convenient than Generator functions returning an Iterator. You can specify what to do next using the then method.

Further, async functions can be thought of as multiple asynchronous operations wrapped as a Promise object, and await commands are syntactic sugar for internal THEN commands.

The system test

one

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');
Copy the code

two

let num = 10
async function foo() {
	console.log('async:'+await new Promise(res= > {
		setTimeout(() = > console.log('async-set'+ ++num),0)
		num++;
		res(num);
		num++
	}));
	console.log('async2:'+num)
}
console.log('1',++num)
console.log('2',num++)
setTimeout(() = >console.log('set1'+num),0)
console.log('c1:'+num)
foo()
console.log('c2:'+num)
setTimeout(() = >console.log('set2:'+num),0)
Copy the code

see

1.Ruan Yifeng – Introduction to ES6