ES6 Generator函数(二)

Generator

Generator.prototype.throw()
  • Generator 函数返回的遍历器对象都有一个throw方法,可以在函数体外抛出错误,然后在Generator 函数体内捕获
    var g = function* () {
    	try {
    		yield;
    	} catch (e) {	//catch只能捕捉一次错误
    		console.log('内部捕获', e);
    	}
    };
    var i = g();
    i.next();
    try {
    	i.throw('a');
    	i.throw('b');
    	} catch (e) {
    	console.log('外部捕获', e);
    }
    			// 内部捕获 a	第一次抛出错误i.throw('a')被Generator函数体内的catch语句捕获
    			// 外部捕获 b	第二次抛出错误i.throw('b'),由于Generator函数内部的catch语句已经执行过了,不会再捕捉到这个错误,所以就抛出了Generator函数体,被函数体外的catch语句捕获
    
  • 如果Generator 函数内部没有部署try…catch代码块,那么throw方法抛出的错误将被外部try…catch代码块捕获.如果Generator 函数内部外部都没有部署try…catch代码块,那么程序将报错,中断执行
  • throw方法被捕获以后,会附带执行下一个yield表达式,也就是说会附带执行一次next方法.也可以说,只要Generator 函数内部部署了try…catch代码块,那么遍历器的throw方法抛出的错误不会影响下一次遍历
  • Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 函数体内抛出的错误,也可以被函数体外的 catch 捕获
  • 一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用 next 方法,将返回一个 value 属性等于 undefined 、 done 属性等于 true 的对象,即 JavaScript 引擎认为这个Generator 已经运行结束了
Generator.prototype.return()
  • Generator 函数返回的遍历器对象还有一个return方法,可以返回给定的值,并且终结遍历Generator 函数

    function* gen() {
    	yield 1;
    	yield 2;
    	yield 3;
    }
    var g = gen();
    g.next() // { value: 1, done: false }
    g.return('foo') // { value: "foo", done: true } 
    //使用return返回的value值是return的参数,且函数遍历终止,done返回true,以后再调用next,done都返回true
    //如果return方法调用不提供参数,则返回值的value属性为undefined
    //如果Generator函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行
    g.next() // { value: undefined, done: true }
    -------------------------------------------------
    //带有try...finally的return例子
    function* numbers () {
    	yield 1;
    	try {
    		yield 2;
    		yield 3;
    	} finally {
    		yield 4;
    		yield 5;
    	}
    	yield 6;
    }
    var g = numbers();
    g.next() // { value: 1, done: false }
    g.next() // { value: 2, done: false }
    g.return(7) // { value: 4, done: false }
    g.next() // { value: 5, done: false }
    g.next() // { value: 7, done: true }
    
next(),throw(),return()的共同点
  • 他们的作用都是让Generator函数恢复执行,并且使用不同的语句替换yield表达式.
    1. next()是将yield表达式替换为一个值
      const g = function* (x, y) {
      	let result = yield x + y;
      	return result;
      };
      const gen = g(1, 2);
      gen.next();  //{value: 3, done: false}
      gen.next(1); //{value: 1, done: true}
      // 相当于将 let result = yield x + y
      // 替换成 let result = 1;
      //如果next方法没有参数,就相当于替换为undefined
      
    2. throw()是将yield表达式替换成一个throw语句
      gen.throw(new Error('出错了')); // Uncaught Error: 出错了
      // 相当于将 let result = yield x + y
      // 替换成 let result = throw(new Error('出错了'));
      
    3. return()是将yield表达式替换成一个return语句
      gen.return(2); // Object {value: 2, done: true}
      // 相当于将 let result = yield x + y
      // 替换成 let result = return 2;
      
yield* 表达式
  • 如果在Generator函数内部调用另一个Generator函数,默认情况下是没有效果的.这时就需要yield* 表达式,用来在一个Generator函数中执行另一个Generator函数

    //两者对比的例子
    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"
    
  • yield* 后面的 Generator 函数(没有 return 语句时),等同于在 Generator函数内部,部署一个 for…of 循环

  • 在有 return 语句时,则需要用 var value = yield* iterator 的形式获取 return 语句的值

    function *foo() {
    	yield 2;
    	yield 3;
    	return "foo";
    }
    function *bar() {
    	yield 1;
    	var v = yield *foo();
    	console.log( "v: " + v );
    	yield 4;
    }
    var it = bar();
    it.next()
    // {value: 1, done: false}
    it.next()
    // {value: 2, done: false}
    it.next()
    // {value: 3, done: false}
    it.next();	//只关注这里的next即可.这里遍历到return后返回"foo",之后还会继续执行下一个yield表达式
    // "v: foo"
    // {value: 4, done: false}
    it.next()
    // {value: undefined, done: true}
    
  • 如果 yield* 后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员

  • 同理,任何具有Iteartor接口的数据结构都可以被yield* 遍历

  • 当yield* 和扩展运算符结合使用

    function* genFuncWithReturn() {
    	yield 'a';
    	yield 'b';
    	return 'The result';
    }
    function* logReturned(genObj) {
    	let result = yield* genObj;
    	console.log(result);
    }
    [...logReturned(genFuncWithReturn())]
    // The result
    // 值为 [ 'a', 'b' ]
    //这里先执行的是logReturned(),因此会先打印其中的result.之后才会执行logReturned中的函数参数,依次解构值'a','b'
    
  • 使用yield* 实现嵌套数组的遍历

    function* iterTree(tree){
    	if(Array.isArray(tree)){
    		for(let i=0;i<tree.length;i++){
    			yield* iterTree(tree[i]);
    		}
    	}else{
    		yield tree;
    	}
    }
    const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
    for(let x of iterTree(tree)){
    	console.log(x); //a,b,c,d,e
    }
    
作为对象属性的Generator 函数
  • 如果一个对象的属性是Generator 函数,可也简写为

    let obj={
    	* fucName(){...}
    }
    //完整形式为
    let obj={
    	fucName : function *(){...}
    }
    
Generator 函数的this
  • Generator 函数总是返回一个遍历器,ES6规定这个遍历器是Generator 函数的实例,也继承了Generator 函数的prototype对象上的方法

    function* g() {}
    g.prototype.hello = function () {
    	return 'hi!';
    };
    let obj = g();
    obj instanceof g // true	Generator 函数g返回的遍历器obj是g的实例
    obj.hello() // 'hi!'	继承了原型上的方法
    //如果把g当作普通构造函数并不会生效,因为g返回的总是遍历器对象,而不是this对象
    function* g() {
    	this.a = 11;
    }
    let obj = g();
    obj.a // undefined
    //Generator函数也不能跟 new 命令一起用,会报错
    function* F() {
    	yield this.x = 2;
    	yield this.y = 3;
    }
    new F()
    // TypeError: F is not a constructor
    
  • 让Generator 函数返回一个正常的对象实例,即可以用next方法,又可以正常获得this

    function* F() {
    	this.a = 1;
    	yield this.b = 2;
    	yield this.c = 3;
    }
    var obj = {};
    var f = F.call(obj);  //通过call来绑定this
    f.next(); // Object {value: 2, done: false}
    f.next(); // Object {value: 3, done: false}
    f.next(); // Object {value: undefined, done: true}
    obj.a // 1
    obj.b // 2
    obj.c // 3
    //这个例子是将所有属性都绑定在obj上了,因此obj成了F的实例
    //可以用F.prototype取代obj,将两者统一
    var f = F.call(F.prototype);
    f.a // 1
    f.b // 2
    f.c // 3
    
  • 再将 F 改成构造函数,就可以对它执行 new 命令了

    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
    
Generator与协程
  • 协程是一种程序运行的方式,可以理解为协作的线程或协作的函数.协程既可以用单线程实现,又可以用多线程实现.前者是一种特殊的子例程,后者是一种特殊的线程
  • 传统的子例程采用堆栈式先进后出的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数.协程与其不同,多个线程(单线程情况下,即多个函数)可以并行执行,但是只有一个线程(或函数)处于正在运行的状态,其他线程(或函数)都处于暂停态,线程(或函数)之间可以交换执行权。也就是说,一个线程(或函数)执行到一半,可以暂停执行,将执行权交给另一个线程(或函数),等到稍后收回执行权的时候,再恢复执行。这种可以并行执行、交换执行权的线程(或函数),就称为协程
  • Generator 函数是 ES6 对协程的实现,但属于不完全实现。Generator 函数被称为半协程,意思是只有 Generator 函数的调用者,才能将程序的执行权还给 Generator 函数.如果将 Generator 函数当作协程,完全可以将多个需要互相协作的任务写成Generator 函数,它们之间使用 yield 表示式交换控制权
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值