generator/yield/next
generator函数即生成器函数,可以理解为用于产生生成器的函数
yield 关键字是只能用于generator函数,作为异步断点,相当于continue+return功能
next 关键字是生成器用于执行生成器函数的关键字,
即每调用一次,执行到yield定义的语句,把yield 后面的表达式结果当做返回值返回来
如果调用时传递了参数,则覆盖上次yield 的结果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function* helloworld(){
yield 'hello';
yield 'world';
let aa = 0;
let a = yield aa++; //yield赋值不用小括号
console.log(a);
yield 123+1;
console.log('Hello' + (yield 123)); //使用yield表达式用小括号
return 'endig';
}
let obj = helloworld();//并不会执行generator函数,只是生成生成器
obj.next();//{value: "hello", done: false}
obj.next();//{value: "world", done: false}
obj.next();//{value: 0, done: false}
obj.next('456');// 456 {value: 124, done: false} 如果不传值的话,a的值为undefined
obj.next();//{value: 123, done: false}
obj.next();// Helloundefined {value: "endig", done: true}; 不传参,yield返回undefined
next()是将yield表达式替换成一个值。
throw()是将yield表达式替换成一个return语句。
return()是将yield表达式替换成一个return语句。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
如果yield表达式后面跟的是一个遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为yield表达式
yield后面的 Generator 函数(没有return语句时),等同于在 Generator 函数内部,部署一个for…of循环。
for…of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46例1
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
例2
let delegatedIterator = (function* () {
yield 'Hello!';
yield 'Bye!';
}());
let delegatingIterator = (function* () {
yield 'Greetings!';
yield* delegatedIterator;
yield 'Ok, bye.';
}());
for(let value of delegatingIterator) {
console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye."
如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。1
2
3
4
5function* gen(){
yield* ["a", "b", "c"];
}
gen().next() // { value:"a", done:false }
上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。
Generator 函数返回的遍历器对象,可以继承prototype,但不能当做普通构造函数,不会返回this1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57function* g() {
this.a = 11;
}
let obj = g();
obj.next();
obj.a // undefined
//可以进行改造
//执行的是遍历器对象f,但是生成的对象实例是obj
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() {
return gen.call(gen.prototype);
}
var f = new F();
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
实际应用
状态机
1 | var clock = function*() { |
异步操作的同步化写法
IMPORTANT!!!
Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
只有当yield后面跟的函数先执行完,无论执行体里面有多少异步回调,都要等所有回调先执行完,才会执行等号赋值,以及再后面的操作。这也是yield最大的特性。
1 | function request(url) { |
第1步:将所有异步代码的每一步都封装成一个普通的、可以有参数的函数,比如上面的request函数。上面例子三个异步代码却只定义了一个request函数,因为request函数能复用。如果不能复用的话,请老老实实定义三个普通函数,函数内容就是需要执行的异步代码。
第2步:定义一个生成器函数,把流程写进去,完全的同步代码的写法。生成器函数可以有参数。
第三步:定义一个变量,赋值为迭代器对象。迭代器对象可以加参数,参数通常将作为流程所需的初始值。
第四步:变量名.next()。不要给这个next()传参数,传了也没用,因为它找不到上一个yield语句。
1 | //在上述每一步异步中间,都间隔3秒 |
async/await
async 用于定义异步函数,相当于generator的*号,只不过async函数不需要像generator那样用next()去出发,只需要调用就能实现异步
await 用于async函数内部,作用是等待await 后面的异步流程结束后,执行asyn函数接下来的语句,相当于暂停符
async返回promise对象,因此可以连接then()或者catch()继续进行异步操作或者捕获错误
async函数内部return语句返回的值,会成为then方法回调函数的参数。
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,
除非遇到return语句或者抛出错误。
也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。即可以等任意表达式的结果
因此 await 返回异步函数return的结果,或者异步函数返回的promise的resolve的参数1
2
3如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。
解决中断执行的方法有两种,
一种是把await放在try…catch…里面,例5
另一种是给await的promise链接catch{},例6
优化:如果两个异步操作没有任何联系,不存在继发关系,最好让他们同时触发 例8,例9
1 | 例1. |