ES6: Generator function detailed explanation
- 1. Concept
- 2. Yield expression
-
- 2.1 Difference between yield statement and return statement
- 2.2 The Generator function does not add a yield statement, and it becomes a purely deferred execution function at this time
- 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
- 2.4 If the yield expression is used in another expression, it must be enclosed in parentheses
- 2.5 The yield expression is used as a parameter or placed on the right side of the assignment expression, without parentheses
- 3. Relationship with Iterator interface
- 4. The parameters of the next() method
- 5. for...of loop
- 6、Generator.prototype.throw()
- 7、Generator.prototype.return()
- 8. yield* expression
- 9. Generator function application examples
-
- 9.1 Using Generator function and for...of loop to realize Fibonacci sequence
- 9.2 Synchronized representation of asynchronous operations
- 9.3 Reading a text file line by line
- 9.4 Control Flow Management
- 9.5 Genarator deploys the Iterator interface to any object
- 9.6 Genarator deploys Iterator interface to arrays
- 10. The difference between promise, generator and aysnc/await
1. Concept
Generator
ES6
An asynchronous programming solution provided by functional style , the syntax behavior is completely different from traditional functions. There are many ways to Generator
understand functions.
In terms of syntax: it can be understood as a state machine, which encapsulates multiple internal states;
in terms of form: Generator
the function is an ordinary function, but it has two characteristics: one is function
an asterisk between the command and the function name *
; two It is the use of statements inside the function body yield
to 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 Generator
the 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; Generator
the function is not executed but returns an Iterator
object, and is traversed by calling the method Iterator
of the object . When the method of the object next
is 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:Iterator
next
yield
return
Generator
yield
next
- The first call,
Generator
the function starts to execute until it encounters the firstyield
statement.next
The method returns an object, itsvalue
attribute isyield
the value of the current statementhello
, anddone
the value of the attributefalse
indicates that the traversal has not yet ended. - For the second call, the function will execute
Generator
from 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.yield
yield
next
value
yield
world
done
false
- For the third call,
Generator
the function will be executed fromyield
the place where the last statement stopped untilreturn
the statement (if there is noreturn
statement, it will be executed until the end of the function).next
The property of the object returned by the methodvalue
is the value of the expression immediatelyreturn
following the statement (if there is noreturn
statement,value
the value of the propertyundefined
), anddone
the value of the propertytrue
indicates that the traversal has ended. - For the fourth call,
Generator
the function has finished running at this time, and the propertynext
of the object returned by the methodvalue
isundefined
,done
and the property istrue
. Calling the method laternext
will return this value.
2. Yield expression
Since Generator
the traverser object returned by the function next
will traverse the next internal state only if the method is called, it actually provides a function that can suspend execution. yield
The statement is the pause flag.
The operation logic of the method of the traverser object next
is as follows.
1. When yield
the statement is encountered, the following operations are suspended, and yield
the value of the expression immediately following is used as the attribute value of the returned object value
.
2. The next time next
the method is called, continue to execute until the next yield
statement is encountered.
3. If there is no new yield
statement, it will run until the end of the function until return
the statement, and use return
the value of the expression after the statement as value
the attribute value of the returned object.
4. If the function does not have a statement, the property value return
of the object is returned .value
undefined
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.iterator
is equal to the traverser object generating function of the object, calling this function will return a traverser object of the object.
Since Generator
the function is an traverser generation function, it can be assigned Generator
to the Symbol.iterator property of the object, so that the object has Iterator
the 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, Generator
the function is assigned to Symbol.iterator
the property, so that myIterable
the object has Iterator
the interface, which can be ...
traversed by operators.
4. The parameters of the next() method
yield
The statement itself has no return value, or it always returns undefined
. next
Methods can take one parameter, which will be treated as yield
the 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 Generator
function that can run infinitely f
. If next
the method has no parameters, every time the statement is run yield
, reset
the value of the variable is always undefined
. When next
the method has a parameter true
, the current variable reset
is reset to this parameter (ie true
), so i
will be equal to -1
, and the next cycle will -1
be incremented from the beginning.
This function has a very important syntactic meaning. The context state ( ) of a function remains unchanged Generator
from the suspended state to the resumed execution . context
Through next
the parameters of the method, there is a way Generator
to continue injecting values into the function book after the function starts running. Generator
That 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...of
The loop can automatically traverse the object Generator
generated by the function Iterator
, and there is no need to call next
the 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...of
a loop to display the values of the 5 yield
statements 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()
Generator
The traverser object returned by the function has a throw
method, which can throw an error outside the function body and then Generator
catch 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 i
throws two errors in a row. The first error is caught by the statement Generator
inside the function body catch
. i
The second time an error is thrown, since the statement Generator
inside the function catch
has already been executed, this error will not be caught again, so this error is thrown out of the function body and captured Generator
by the statement outside the function .catch
7、Generator.prototype.return()
Generator
The iterator object returned by the function also has a return
method that returns the given value and terminates Generator
the 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 g
calls return
the method, value
the property of the return value is return
the parameter of the method foo
. At the same time, Generator
the traversal of the function is terminated, and the property of the return value done
is true
, and the method is called later next
, done
the property is always returned true
.
If return
the method is called with no parameters, the return value vaule
has 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 Generator
another Generator
function 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, foo
and bar
are both Generator
functions, bar
calling inside foo
will have no effect.
yield*
At this time , statements are needed to Generator
execute another Generator
function 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 examples
9.1 Using Generator function and for...of loop to realize Fibonacci sequence
function* fibonacci() {
let [prev,curr] = [0,1];
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
Generator
The effect of suspending the execution of the function means that the asynchronous operation can be written in yield
the expression, and then next
executed 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 yield
under the expression, and it will not be next
executed until the method is called anyway. Therefore, Generator
an 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 main
is to Ajax
obtain 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 makeAjaxCall
in the function next
must add response
parameters, because yield
the 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 yield
the 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 Promise
syntax. Generator
Functions 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 promise
that 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);
}