Generator
基本概念
- 异步编程的一种解决方案
- 异步的编程方式:回调(早期的异步编程方式)、Promise、Generator
- Generator相对Promise更高级些
- next和yield*都是Generator内的一部分
- Generator里包含多个步骤,它遇到哪个步骤,哪个步骤的标志就是yield或者是return,它遇到yield或者return就停止了,这和普通的函数有很大的区别
- Generator要进行下一步的时候,就调用next函数去进行下一步,直到结束
next函数的用法
yield*的语法
{
//generator基本定义
let tell = function* (){//function后面有个*//tell可以理解为是Generator函数
//函数体有 yield语句
yield 'a';
yield 'b';
return 'c';
}
//以上就是Generator定义的基本方式
let k = tell();//函数的执行都是圆括号()
console.log(k.next());//通过next()不断的执行函数体的每个阶段
console.log(k.next());
console.log(k.next());
console.log(k.next());
//{value: "a", done: false}
//{value: "b", done: false}
//{value: "c", done: true}
//{value: undefined, done: true}
//Generator函数返回的就是一个Iterator接口
//通过next()的方式不断的去做函数体的几个阶段
//也就是说,在执行tell()的时候,它会在遇到第一个yield的时候,停下来,执行完yield之前的语句(这个demo中没有写yield之前的语句),等你调用next()的时候,它会执行第一个yield,当你调用第二个next(),它会执行接下来的一个yield或者return,以此类推,从而保证这个函数体内部看上去是一个异步操作的过程,这就是Generator的基本用法
}
Generator函数与Iterator接口的关系
- 因为任意一个对象的Iterator接口都是部署在Symbol.iterator属性上
- Generator函数就是一个遍历器生成函数,所以呢,我们可以直接把它赋值给Symbol.iterator ,从而使这个对象也具有Iterator接口
- Iterator接口手动自定义,现在补充一种方式:Generator也可以作为遍历器的返回值
{
//定义一个空对象,对象没有Iterator接口,不能使用for...of
let obj = {}//要使用for...of必须手动部署一个Interator接口
//通过创建Genarator函数的方式给obj部署Interator接口
obj[Symbol.iterator]=function* (){
yield 1;
yield 2;
yield 3;
}
//以上是Generator新的应用,也是Generator与Interator的关系
//相对于手动部署,Generator方法简洁很多
for(let value of obj){
console.log('value',value)
//1
//2
//3
}
//以上,先 生成一个空对象,然后通过Generator函数生成三个步骤,然后通过for...of遍历到三个值,这是Generator新的应用
}
Generator应用场景和优势
- 状态机(js编程中,状态机是比较高级的用法)
比如说,用三种状态描述一个事物,
这个事物只存在三种状态,A=>B,B=>C,C=>A;三个之间循环永远跑步出第四种状态出来,使用Generator处理这种状态机,是特别适用的
{
let state = function* (){
while(1){//while(1)循环,永远在这个步骤里
yield 'A';
yield 'B';
yield 'C';
}
}
let status = state();
console.log(status.next());
console.log(status.next());
console.log(status.next());
console.log(status.next());
console.log(status.next());
//{value: "A", done: false}
//{value: "B", done: false}
//{value: "C", done: false}
//{value: "A", done: false}
//{value: "B", done: false}
//这样就一直循环下去
}
//async await 语法的写法
//asyc await 是 Generator的语法糖
//asyc await 并不是异步编程方式,而是Generator的语法糖
//执行asyc await 语法,需要按照babel的一些插件
{
let state =async function (){
while(1){//while(1)循环,永远在这个步骤里
await 'A';
await 'B';
await 'C';
}
}
let status = state();
console.log(status.next());
console.log(status.next());
console.log(status.next());
console.log(status.next());
console.log(status.next());
//{value: "A", done: false}
//{value: "B", done: false}
//{value: "C", done: false}
//{value: "A", done: false}
//{value: "B", done: false}
}
Generator应用场景——抽奖
当前账户下,用户还可以抽奖5次
用户点击一次,抽奖变成4,3,…
抽奖剩余次数的逻辑,前端要做限制,服务端也要做限制
//前端做抽奖剩余次数的限制
{
//抽奖业务这块(即抽奖逻辑)
let draw = function(count){//draw函数实现当前的逻辑和剩余次数的显示
//具体抽奖逻辑(不写,根据具体业务自行完成)
//输出剩余次数
console.info(`剩余${count}次`)
}
//怎么去计算还剩多少次呢?
//之前的做法可能是设置一个全局的变量来保存当前的次数
//设置全局变量是非常不安全的,会被修改变量的风险,少把存储数据放在全局变量上,这会影响页面性能的
//如何使用Generator处理这个业务?
let residue=function* (count){//通过Generator控制次数
while (count>0) {//while对次数的限制
count--;
yield draw(count);//yield执行具体的抽奖逻辑
}
}
//Generator实例化
//通过按钮绑定起来
//每点击一次执行一次next(),next就是抽奖,当次数用完以后,再怎么点,抽奖逻辑也不会有反应
let star = residue(5);//5应该是服务端传递给前端的一个数
let btn=document.createElement('button')
btn.id='start';
btn.textContent='抽奖';
document.body.appendChild(btn);
document.getElementById('start').addEventListener('click',function(){
star.next();
},false)
//使用Generator好处是,次数没有保存在全局变量中
}
Generator应用场景——长轮询
- 长轮询:比如说服务端的某一个数据状态,定期的去变化,前端需要定时的去服务端取这个状态,因为HTTP是无状态的连接;
- 如何取得服务端实时的变化呢,长轮询和websocket两种方式
- 因为websocket浏览器兼容性不好,所以长轮询还是普遍的用法
- 长轮询普遍做法是通过定时器不断的访问这个接口;
- 现在使用Generator完成长轮询同时区分业务相关逻辑
//长轮询、模拟ajax过程
{
//对接口的模拟
let ajax = function* (){
yield new Promise(function(resolve,reject){
setTimeout(function(){
resolve({code:1})
},2000);
})
}
//轮询的过程
let pull = function(){
let generator=ajax();//Generator实例化
let step = generator.next();//取得generator的步骤
//generator.next() 这个使yield运行一次,获取Promise实例,Promise实例是对服务端接口连接查询一次;
step.value.then(function(d){//这个step.value就是Promise实例
//d就是从服务端拿回的数据(即{code:0})
if(d.code != 0){//判断数据拿到的是不是最新的,如果不是就再请求
setTimeout(function(){
console.info('wait')
pull()
},1000);
}else{//如果拿到最新数据长轮询查询结束
console.info(d);
}
})
}
pull();//这个步骤,相当于执行了一次轮询
}
//以上通过Generator与Promise结合,简化长轮询