ES6: Generator function detailed explanation

1. Concept

GeneratorES6An asynchronous programming solution provided by functional style , the syntax behavior is completely different from traditional functions. There are many ways to Generatorunderstand functions.
In terms of syntax: it can be understood as a state machine, which encapsulates multiple internal states;
in terms of form: Generatorthe function is an ordinary function, but it has two characteristics: one is functionan asterisk between the command and the function name *; two It is the use of statements inside the function body yieldto define different internal states.

// 传统函数
function fn() {
    
    
	return 'hello world'
}
fn()  // 'hello world',一旦调用立即执行

// Generator函数
function* helloWorldGenerator () {
    
    
	yield 'hello'
	yield 'world'
	return 'ending'
}

const hw = helloWorldGenerator()  // 调用 Generator函数,函数并没有执行,返回的是一个Iterator对象
console.log(hw.next())  // {value: 'hello', done: false},value 表示返回值,done 表示遍历还没有结束
console.log(hw.next())  // {value: 'world', done: false},value 表示返回值,done 表示遍历还没有结束
console.log(hw.next())  // {value: 'ending', done: true},value 表示返回值,done 表示遍历还没有结束
console.log(hw.next())  // {value: undefined, done: true},value 表示返回值,done 表示遍历结束

From the above code, we can see that Generatorthe operation of the traditional function is completely different from that of the function. The traditional function is executed immediately after being called and the return value is output; Generatorthe function is not executed but returns an Iteratorobject, and is traversed by calling the method Iteratorof the object . When the method of the object nextis called for the first time , the internal pointer will start execution from the head of the function or where it stopped last time, until the next expression or statement is encountered. In other words, functions are executed in pieces, expressions are markers that suspend execution, and methods can resume execution. The execution process is as follows:IteratornextyieldreturnGeneratoryieldnext

  • The first call, Generatorthe function starts to execute until it encounters the first yieldstatement. nextThe method returns an object, its valueattribute is yieldthe value of the current statement hello, and donethe value of the attribute falseindicates that the traversal has not yet ended.
  • For the second call, the function will execute Generatorfrom the place where the last statement stopped until the next statement. The property of the object returned by the method is the value of the current statement , and the value of the property indicates that the traversal has not yet ended.yieldyieldnextvalueyieldworlddonefalse
  • For the third call, Generatorthe function will be executed from yieldthe place where the last statement stopped until returnthe statement (if there is no returnstatement, it will be executed until the end of the function). nextThe property of the object returned by the method valueis the value of the expression immediately returnfollowing the statement (if there is no returnstatement, valuethe value of the property undefined), and donethe value of the property trueindicates that the traversal has ended.
  • For the fourth call, Generatorthe function has finished running at this time, and the property nextof the object returned by the method valueis undefined, doneand the property is true. Calling the method later nextwill return this value.

2. Yield expression

Since Generatorthe traverser object returned by the function nextwill traverse the next internal state only if the method is called, it actually provides a function that can suspend execution. yieldThe statement is the pause flag.
The operation logic of the method of the traverser object nextis as follows.
1. When yieldthe statement is encountered, the following operations are suspended, and yieldthe value of the expression immediately following is used as the attribute value of the returned object value.
2. The next time nextthe method is called, continue to execute until the next yieldstatement is encountered.
3. If there is no new yieldstatement, it will run until the end of the function until returnthe statement, and use returnthe value of the expression after the statement as valuethe attribute value of the returned object.
4. If the function does not have a statement, the property value returnof the object is returned .valueundefined

Note: The expression behind the yield statement will only be executed when the next method is called and the internal pointer points to the statement. Therefore, it is equivalent to providing JavaScript with a manual "lazy evaluation" syntax function.

function* gen() {
    
    
	yield 123 + 456;
}

In the above code, the expression 123 + 456 after yield will not be evaluated immediately, but will only be evaluated when the next method moves the pointer to this sentence.

2.1 Difference between yield statement and return statement

The yield statement has both similarities and differences from the return statement. Similar in that both return the value of the expression immediately following the statement. The difference is that every time the vield function is encountered, the execution will be suspended, and the next time it will continue to execute backward from this position, while the return statement does not have the function of position memory. Only one (or one) return statement can be executed in a function, but multiple (or multiple) yield statements can be executed. Normal functions can only return one value, because the return statement can only be executed once; Generator functions can return a series of values, because there can be any number of yield statements. From another point of view, it can also be said that Generator generates a series of values, which is the origin of its name (the word "generator" in English means "generator").

2.2 The Generator function does not add a yield statement, and it becomes a purely deferred execution function at this time

function* f () {
    
    
	console.log('执行了')
}
var generator = f()
setTimeout(function () {
    
    
	generator.next()
}, 2000)

In the above code, if the function f is an ordinary function, it will be executed when assigning a value to the variable generator. But the function f is a Generator function, so it will only be executed when the next method is called.

2.3 The yield expression can only be used in the Generator function, and it will report an error if it is used in other places

{
    
    
  (function (){
    
    
    yield 1;
  })()

  // SyntaxError: Unexpected number
  // 在一个普通函数中使用yield表达式,结果产生一个句法错误
}

2.4 If the yield expression is used in another expression, it must be enclosed in parentheses

{
    
    
	function* demo() {
    
    
		console.log('Hello' + yield); // SyntaxError
		console.log('Hello' + yield 123); // SyntaxError
		
		console.log('Hello' + (yield)); // OK
		console.log('Hello' + (yield 123)); // OK
	}
}

2.5 The yield expression is used as a parameter or placed on the right side of the assignment expression, without parentheses

{
    
    
	function* demo() {
    
    
		foo(yield 'a', yield 'b'); // OK
		let input = yield; // OK
	}
}

3. Relationship with Iterator interface

The method of any object Symbol.iteratoris equal to the traverser object generating function of the object, calling this function will return a traverser object of the object.
Since Generatorthe function is an traverser generation function, it can be assigned Generatorto the Symbol.iterator property of the object, so that the object has Iteratorthe interface.

var myIterable = {
    
    }
myIterable[Symbol.iterator] = function* () {
    
    
	yield 1
	yield 2
	yield 3
}
 for(let value of myIterable) {
    
    
 	console.log(value)
 }
 // 1
 // 2
 // 3
[...myIterable]  // [1, 2, 3]

In the above code, Generatorthe function is assigned to Symbol.iteratorthe property, so that myIterablethe object has Iteratorthe interface, which can be ...traversed by operators.

4. The parameters of the next() method

yieldThe statement itself has no return value, or it always returns undefined. nextMethods can take one parameter, which will be treated as yieldthe return value of the previous statement.

function* f () {
    
    
    for (var i = 0; true; i++) {
    
    
		var reset = yield i
    	if (reset) {
    
    
      		console.log('执行了')
      		i = -1
   		 }
  	 }
}
var g = f()
g.next()  // {value: 0, done: false}
g.next()  // {value: 1, done: false}
g.next(true)  // {value: 0, done: false}

The above code first defines a Generatorfunction that can run infinitely f. If nextthe method has no parameters, every time the statement is run yield, resetthe value of the variable is always undefined. When nextthe method has a parameter true, the current variable resetis reset to this parameter (ie true), so iwill be equal to -1, and the next cycle will -1be incremented from the beginning.

This function has a very important syntactic meaning. The context state ( ) of a function remains unchanged Generatorfrom the suspended state to the resumed execution . contextThrough nextthe parameters of the method, there is a way Generatorto continue injecting values ​​into the function book after the function starts running. GeneratorThat is to say, different values ​​can be injected from the outside to the inside at different stages of the function's operation, so as to adjust the behavior of the function.
Let's look at another example.

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

var a = foo(5)
a.next()  // 首次调用next,函数只会执行到 “yield(5+1)” 暂停,并返回 {value: 6, done: false}
a.next()  // 第二次调用next,没有传递参数,所以 y的值是undefined,那么 y/3 当然是一个NaN,所以应该返回 {value: NaN, done: false}
a.next()  // 同样的道理,z也是undefined,6 + undefined + undefined = NaN,返回 {value: NaN, done: true}

var b = foo(5)
b.next()  // 正常的运算应该是先执行圆括号内的计算,再去乘以2,由于圆括号内被 yield 返回 5 + 1 的结果并暂停,所以返回{value: 6, done: false}
b.next(12)  // 上次是在圆括号内部暂停的,所以第二次调用 next方法应该从圆括号里面开始,就变成了 let y = 2 * (12),y被赋值为24,所以第二次返回的应该是 24/3的结果 {value: 8, done: false}
b.next(13)  // 参数2被赋值给了 z,最终 x + y + z = 5 + 24+ 13 = 42,返回 {value: 42, done: true}

5. for...of loop

for...ofThe loop can automatically traverse the object Generatorgenerated by the function Iterator, and there is no need to call nextthe method at this time.

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

The above code uses for...ofa loop to display the values ​​of the 5 yieldstatements in sequence.
Note: Once the done property of the returned object of the next method is true, the for…of loop will terminate without including the returned object, so the 6 returned by the above return statement is not included in the for…of loop.

6、Generator.prototype.throw()

GeneratorThe traverser object returned by the function has a throwmethod, which can throw an error outside the function body and then Generatorcatch it in the function body.

var g = function* () {
    
    
	try (
		yield;
	} catch (e) {
    
    
		console.log('内部捕获’,e);
	}
}var i=g();
i.next();

try {
    
    
	i.throw('a');
	i.throw('b');
} catch (e) {
    
    
	console.log('外部捕获’,e);
}
//内部捕获a
// 外部捕获b

In the code above, the traverser object ithrows two errors in a row. The first error is caught by the statement Generatorinside the function body catch. iThe second time an error is thrown, since the statement Generatorinside the function catchhas already been executed, this error will not be caught again, so this error is thrown out of the function body and captured Generatorby the statement outside the function .catch

7、Generator.prototype.return()

GeneratorThe iterator object returned by the function also has a returnmethod that returns the given value and terminates Generatorthe function's traversal.

function* gen() {
    
    
	yield 1;
	yield 2;
	yield 3;
}
var g = gen();
g.next ()  // { value: 1, done: false }
g.return('foo')  // { value:"foo",done: true }
g.next()  // { value: undefined, done: true }

In the above code, after the traverser object gcalls returnthe method, valuethe property of the return value is returnthe parameter of the method foo. At the same time, Generatorthe traversal of the function is terminated, and the property of the return value doneis true, and the method is called later next, donethe property is always returned true.
If returnthe method is called with no parameters, the return value vaulehas a property of undefined.

function* gen() {
    
    
	yield 1;
	yield 2;
	yield 3;
}
var g = gen();
g.next()  // { value: 1,done: false }
g.return()  // { value: undefined,done: true }

8. yield* expression

If Generatoranother Generatorfunction is called inside a function, it has no effect by default.

function* foo() {
    
    
	yield 'a';
	yield'b';
}
function* bar() {
    
    
	yield'x';
	foo();
	yield 'y';
}
for (let v of bar()){
    
    
	console.log(v);
}
//"x"
//"y"

In the above code, fooand barare both Generatorfunctions, barcalling inside foowill have no effect.

yield*At this time , statements are needed to Generatorexecute another Generatorfunction in one function.

function* bar() {
    
    
	yield 'x';
	yield* foo();
	yield'y';
}
// 等同于
function* bar() [
	yield 'x';
	yield 'a';
	yield 'b';
	yield 'y';
}
// 等同于
function* bar() {
    
    
	yield 'x';
	for (let v of foo()) {
    
    
		yield v;
	}
	yield 'y';
}
for (let v of bar()) {
    
    
	console.log(v);
}
// 'x';
// 'a';
// 'b';
// 'y';

9. Generator function application example

9.1 Using Generator function and for...of loop to realize Fibonacci sequence

function* fibonacci() {
    
    
	let [prev,curr] = [01];
	for (;;) {
    
    
		[prev, curr] = [curr, prev + currl;
		yield curr;
	}
}
for (let n of fibonacci()) {
    
    
	if (n > 1000) break;
	console.log(n);
}
// 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

9.2 Synchronized representation of asynchronous operations

GeneratorThe effect of suspending the execution of the function means that the asynchronous operation can be written in yieldthe expression, and then nextexecuted later when the method is called. This is actually equivalent to not needing to write a callback function, because the subsequent operation of the asynchronous operation can be placed yieldunder the expression, and it will not be nextexecuted until the method is called anyway. Therefore, Generatoran important practical significance of functions is to handle asynchronous operations and rewrite callback functions.

function* main() {
    
    
	var result = yield request('http://some.url');
	var resp = JSON.parse(result);
	console.log(resp.value);
}
function request(url) {
    
    
	makeAjaxCall(url, function(response){
    
    
		it.next(response);
	});
}
var it = main();
it.next();

The function of the above code mainis to Ajaxobtain data through the operation. It can be seen that, except for one more yield, it is almost exactly the same as the synchronous operation.
Note that the method makeAjaxCallin the function nextmust add responseparameters, because yieldthe expression itself has no value and is always equal undefined.

9.3 Reading a text file line by line

function* numbers() {
    
    
	let file = new FileReader("numbers.txt");
	try {
    
    
		while(!file.eof) {
    
    
			yield parseInt(file.readLine()10);
		}
	} finally {
    
    
		file.close();
	}
}

The above code opens the text file, and yieldthe statement can be used to manually read the file line by line.

9.4 Control Flow Management

If a multi-step operation is very time-consuming, if you use a callback:

step1(function (value1) {
    
    
	step2(value1, function(value2) {
    
    
		step3(value2, function(value3) {
    
    
			step4(value3, function(value4) {
    
    
				// Do something with value4
			});
		});
	});
});

Using Promise to rewrite the above code as follows:

Promise.resolve(step1)
	.then(step2)
	.then(step3)
	.then(step4)
	.then(function (value4) {
    
    
	// Do something with value4
	}, function (error) {
    
    
	// Handle any error from step1 through step4
	}).done();

The above code has changed the callback function into a straight-line execution form, but added a lot of Promisesyntax. GeneratorFunctions can further improve the flow of code execution.

function* longRunningTask(valuel) {
    
    
	try {
    
    
		var value2 = yield stepl(valuel);
		var value3 = yield step2(value2);
		var value4 = yield step3(value3);
		var value5 = yield step4(value4);
		// Do something with value4
	} catch (e) {
    
    
		// Handle any error from stepl through step4
	}
}

9.5 Genarator deploys the Iterator interface to any object

function* deployObjectInterface(obj){
    
    
    let keys = Object.keys(obj);
    for(let i=0; i<keys.length; i++){
    
    
        let key = keys[i];
        yield [key, obj[key]];
    }
}
let obj = {
    
    name:"jow", age:21 };
for(let[key, value] of deployObjectInterface(obj)){
    
    
    console.log(key, value); 
}
// name jow
// age 21

9.6 Genarator deploys Iterator interface to arrays

function* deployArrayInterface(arr){
    
    
    var nextIndex = 0;
    while(nextIndex < arr.length){
    
    
        yield arr[nextIndex++];
    }
}
var arr = deployArrayInterface(['name', 'age']);

console.log(arr.next());       // {value: "name", done: false}
console.log(arr.next().value); // name
console.log(arr.next().done);  // false

console.log(arr.next().value); // age
console.log(arr.next().done);  // true

console.log(arr.next().value); // undefined
console.log(arr.next().done);  // true

10. The difference between promise, generator and aysnc/await

All three are solutions for asynchronous programming. The difference is that the one promisethat came out earlier, the second generator, and the last async/await. The three symbolize the evolution of the front-end to solve asynchronous programming.

10.1 promise

 promise比较简单,也是最常用的,主要就是将原来用回调函数异步编程的方法转成relsove和reject触发事件;
    对象内含有四个方法,then()异步请求成功后
	    catch()异步请求错误的回调方法
	    finally()请求之后无论是什么状态都会执行
	    resolve()将现有对象转换为Promise对象
	    all()此方法用于将多个Promise实例包装成一个新的promise实例。
	    race()也是将多个Promise实例包装成一个新的promise实例
	    reject()返回一个状态为Rejected的新Promise实例。
    优点:让回调函数变成了规范的链式写法,程序流程可以看的很清楚
    缺点:编写的难度比传统写法高,阅读代码也不是一眼可以看懂

10.2 Generator

generator是一个迭代生成器,其返回值为迭代器(lterator),ES6标准引入的新的数据类型,主要用于异步编程,它借鉴于Python中的generator概念和语法;

generator函数内有两个重要方法,1 yield表达式 2.next()

Generator 函数是分段执行的,yield表达式是暂停执行的标记,而 next方法可以恢复执行

优点:1.利用循环,每调用一次,就使用一次,不占内存空间 2.打破了普通函数执行的完整性
缺点: 需要用next()方法手动调用,直接调用返回无效iterator 2.

10.3 async/await

   
async:异步函数
await:同步操作

es7中提出来的异步解决方法,是目前解决异步编程终极解决方案,以promise为基础,其实也就是generator的高级语法糖,本身自己就相当于一个迭代生成器(状态机),它并不需要手动通过next()来调用自己,与普通函数一样

async就相当于generator函数中的*,await相当于yield,

async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
function getSomething() {
    
    
	return "something";
}

async function testAsync() {
    
    
	return Promise.resolve("hello async");
}

async function test() {
    
    
	//await是在等待一个async函数完成
	const v1 = await getSomething();
	//await后面不仅可以接Promise,还可以接普通函数或者直接量
	const v2 = await testAsync();
	console.log(v1, v2);
}

おすすめ

転載: blog.csdn.net/DZQ1223/article/details/131993484