JavaScript学习之Generator函数

JavaScript中的Generator函数

ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。

  • 异步编程
  • 所谓“异步”,简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段
  • 异步编程方式:
    (1)回调函数
    (2)事件监听
    (3)发布/订阅者
    (4)Promise对象
  • 所谓回调函数,就是把第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数

回调函数的异步方式容易形成多重嵌套,多个异步操作形成了强耦合,只要有一个操作需要修改,它的上层回调函数和下层回调函数,可能都要跟着修改。这种情况就称为”回调函数地狱”(callback hell)

Promise可以解决callback hell问题,Promise对象允许回调函数的嵌套,改成链式调用

  • Generator的定义
  • 语法上,可以把理解成,Generator 函数是一个状态机,封装了多个内部状态
  • 形式上,Generator 函数是一个普通函数
  • 整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器,异步操作需要暂停的地方,都用yield语句
  • Generator函数特征:
    (1)function 关键字和函数之间有一个星号(*),且内部使用yield表达式,定义不同的内部状态
    (2)调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象
  • Generator 函数的用法
    →定义:
function* fn(){   // 定义一个Generator函数
    yield 'hello';
    yield 'world';
    return 'end';
}
var f1 =fn();           // 调用Generator函数
console.log(f1);        // fn {[[GeneratorStatus]]: "suspended"}
console.log(f1.next()); // {value: "hello", done: false}
console.log(f1.next()); // {value: "world", done: false}
console.log(f1.next()); // {value: "end", done: true}
console.log(f1.next()); // {value: undefined, done: true}
但是,调用Generator函数后,函数并不执行,返回的也不是函数执行后的结果,而是一个指向内部状态的指针对象。 
下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。
即:每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。 

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

Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。

执行一个真实的异步任务

var fetch = require('node-fetch');

function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}

Generator 函数封装了一个异步操作,该操作先读取一个远程接口,
然后从 JSON 格式的数据解析信息。
这段代码非常像同步操作,除了加上了 yield 命令
执行这段代码的方法如下:

var g = gen();
var result = g.next();

result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
});

首先执行 Generator 函数,获取遍历器对象,
然后使用 next 方法(第二行),执行异步任务的第一阶段。
由于 Fetch 模块返回的是一个 Promise 对象,因此要用 then 方法调用下一个next 方法。

可以看到,虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。

🎈yield表达式和next()方法

  • Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。

  • yield表达式就是暂停标志

  • yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行

使用yield需注意: 
(1yield语句只能用于function* 的作用域,
     如果function* 的内部还定义了其他的普通函数,则函数内部不允许使用yield语句
(2yield语句如果参与运算,必须用括号括起来
return方法跟next方法的区别:1return终结遍历,之后的yield语句都失效;next返回本次yield语句的返回值
(2return没有参数的时候,返回{ value: undefined, done: true };next没有参数的时候返回本次yield语句的返回值
(3return有参数的时候,覆盖本次yield语句的返回值,也就是说,返回{ value: 参数, done: true };
     next有参数的时候,覆盖上次yield语句的返回值,
     返回值可能跟参数有关(参数参与计算的话),也可能跟参数无关(参数不参与计算)

遍历器对象的next方法的运行逻辑:

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined
next()方法参数 :

 表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的
 V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的
 从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数
forof循环 :
 可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。
 一旦next方法的返回对象的done属性为trueforof循环就会中止,且不包含该返回对象。

✨示例理解:

// 定义一个Generator函数
function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
console.log(a.next()); 
// Object{value:6, done:false} 第二次运行next方法的时候不带参数,导致y的值等于2 * undefined(即NaN),除以3以后还是NaN

console.log(a.next()); // Object{value:NaN, done:false} 第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN。
console.log(a.next()); // Object{value:NaN, done:true}

var b = foo(5);
console.log(b.next());   // {value:6, done:false } 第一次调用b的next方法时,返回x+1的值6

console.log(b.next(12)); // {value:8, done:false } 第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;
console.log(b.next(13)); // {value:42, done:true } 第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42
  • Generator 函数的数据交换和错误处理
    Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。
    除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换错误处理机制
    1、函数体内外的数据交换
next 方法返回值的 value 属性,是 Generator 函数向外输出数据;
next 方法还可以接受参数,这是向 Generator 函数体内输入数据

function* gen(x){
  var y = yield x + 2;
  return y;
}

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

第一个 next 方法的 value 属性,返回表达式 x + 2 的值(3)
第二个 next 方法带有参数2,这个参数可以传入 Generator 函数,作为上个阶段异步任务的返回结果,被函数体内的变量 y 接收
因此,这一步的 value 属性,返回的就是2(变量 y 的值)。

2、函数体内外的错误处理机制

Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误

function* gen(x){
  try {
    var y = yield x + 2;
  } catch (e){ 
    console.log(e);
  }
  return y;
}

var g = gen(1);
g.next();
g.throw'出错了';
// 出错了

代码的最后一行,Generator 函数体外,使用指针对象的 throw 方法抛出的错误,可以被函数体内的 try ... catch 代码块捕获。
这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值