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表达式.
- 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
- throw()是将yield表达式替换成一个throw语句
gen.throw(new Error('出错了')); // Uncaught Error: 出错了 // 相当于将 let result = yield x + y // 替换成 let result = throw(new Error('出错了'));
- return()是将yield表达式替换成一个return语句
gen.return(2); // Object {value: 2, done: true} // 相当于将 let result = yield x + y // 替换成 let result = return 2;
- next()是将yield表达式替换为一个值
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 表示式交换控制权