Generator函数是ES6的一个很重要的新特性。它可以控制函数的内部状态,依次遍历每个状态;可以根据需要,轻松地让函数暂停执行或者继续执行,根据这个特点,我们可以利用Generator函数来实现异步操作的效果。
- 原理是:利用Generator函数暂停执行的作用,可以将异步操作的语句写到yield后面,通过执行next方法进行回调。
function* (){}
Generator函数的基本用法
1
2<script src="babel-core/browser.min.js" ></script>
<script src="node_modules/babel-polyfill/dist/polyfill.min.js" ></script> - Generator函数,又称生成器函数,是ES6的一个重要的新特性。
- yield:每当执行完一条yield语句后函数就会自动停止,直到再次调用next()方法才会再次执行下一次的yieid语句
- return 终止执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21let tell = function* (){
yield 'a';
yield 'b';
return 'c';
}
//调用tell函数
let k = tell()
//结果:[object Generator]
console.log(k.next())
//{value: "a", done: false}
console.log(k.next())
//{value: "b", done: false}
console.log(k.next())
//{value: "c", done: true}
console.log(k.next())
//{value: undefined, done: true}注意:普通函数用function来声明,Generator函数用function*声明。
Generator函数内部有新的关键字:yield,普通函数没有
你可以把Generator函数被调用后得到的生成器理解成一个遍历器iterator,用于遍历函数内部的状态
Generator函数的行为
- Generator函数被调用后并不会一直执行到最后,它是先回返回一个生成器对象,然后hold住不动,等到生成器对象的next( )方法被调用后,函数才会继续执行,直到遇到关键字yield后,又会停止执行,并返回一个Object对象,然后继续等待,直到next( )再一次被调用的时候,才会继续接着往下执行,直到done的值为true。
yield语句的作用
- 而yield在这里起到了十分重要的作用,就相当于暂停执行并且返回信息。有点像传统函数的return的作用,但不同的是普通函数只能return一次,但是Generator函数可以有很多个yield。而return代表的是终止执行,yield代表的是暂停执行,后续通过调用生成器的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//声明Generator函数:gen1
function* gen1() {
yield "gen1 start";
yield "gen1 end";
}
//声明Generator函数:gen2
function* gen2() {
yield "gen2 start";
yield "gen2 end";
}
//声明Generator函数:start
function* start() {
yield "start";
yield* gen1();
yield* gen2();
yield "end";
}
//调用start函数
var ite = start();
//创建一个生成器
ite.next();
//{value: "start", done: false}
ite.next();
//{value: "gen1 start", done: false}
ite.next();
//{value: "gen1 end", done: false}
ite.next();
//{value: "gen2 start", done: false}
ite.next();
//{value: "gen2 end", done: false}
ite.next();
//{value: "end", done: false} - 这里使用了关键字yield*来实现调用另外两个Generator函数。从后面的多个next( )方法得到的结果看,我们可以知道:
- 如果一个Generator函数A执行过程中,进入(调用)了另一个Generator函数B,那么会一直等到Generator函数B全部执行完毕后,才会返回Generator函数A继续执行
Generator函数和iterator接口的关系
- 所有的iterator接口都u\部署在了[Symbol.iterator这个属性上
- Generator函数就是一个遍历器生成函数 所以可以直接赋值给Symbol的iterator,使对象具备iterator接口
1
2
3
4
5
6
7
8
9let obj = {}
obj[Symbol.iterator] = function* (){
yield '1';
yield '2';
yield '3';
}
for(let key of obj){
console.log(key)//1,2,3
}使用场景 状态机
- 我们需要有a b c 三种状态描述一种事物,这件事物只有三种状态 a到b、b到c、c到a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18let start = function* (){
while (1){
yield 'A';
yield 'B'
yield 'C'
}
}
let status = start()
console.log(status.next())
//A
console.log(status.next())
//B
console.log(status.next())
//C
console.log(status.next())
//A
console.log(status.next())
//B使用案例 抽奖
- 假设当前账户下还可以抽奖5次 前端对次数要做一下限制
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
28let draw = function(count){
//具体抽奖逻辑
console.log(`剩余抽奖${count}次`)
}
let residue = function*(count){
while(count>0){//Generator对抽奖次数进行限制
count--;
yield draw(count);//执行抽奖的逻辑
}
}
//5 实际开发中应该是后台传递给前端的一个数
let star = residue(5)//将Generator实例化
let btn = document.createElement('button')
btn.id = 'start';
btn.textContent=' 抽奖';
document.body.appendChild( \btn)
document.getElementById('start').addEventListener('click',function(){
star.next()
},false)
打印结果;
//剩余抽奖4次
//剩余抽奖3次
//剩余抽奖2次
//剩余抽奖1次
//剩余抽奖0次 - 还有抽奖次数应该是后台传递 不能全局保存 别人修改变量就拦不住了
- 尽量少把数据放到全局对象上 这样对性能不好
使用案例 长轮询
- 服务端的某一个数据状态定期的去变化,我们前端要定时去服务端去取这个状态,因为http是无状态的连接,我们怎么样才能实时的去到服务端的这种变化呢?
- 长轮询是普遍的用法,之前的做法是弄个定时器不断的去访问接口。那么通过Generator怎么去实现这个常轮询 并且代码优雅,并把业务逻辑相关的东西区分开
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//模拟ajax过程
let ajax = function*(){
yield new Promise(function(resolve,reject){
//实际代码中可以更改真实的接口然后resolve参数 pull可以直接调用
setTimeout(function(){
resolve({code:0});//返回接口数据等于0
},200)
})
}
//长轮询的过程 执行后端通信第一次
let pull = function*(){
let generator = ajax()
let step = generator.next()
step.value.then(function(d){//Promise实例
if(d.code != 0){
setTimeout(function(){
console.log('查询中')
pull()
},1000)
}else{
console.log(d)
}
})
}
pull()//调用方法
//结果{code: 0} - 将上边的code不等于0的时候在运行方法会发现一直打印查询中