Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。 Generator函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号,二是,函数体内部使用yield语句,定义不同的内部状态。
一、用法

function* helloWorldGenerator(){ 
 yield ‘hello’; 
 yield ‘world’; 
 return ‘ending’; 
 } 
 var hw = helloWorldGenerator();


上面代码定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(hello和world),即该函数有三个状态:hello,world 和 return 语句(结束执行)。
下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

hw.next() 
 // {value:’hello’, done:false} 
 hw.next() 
 // {value:’world’, done:false} 
 hw.next() 
 // {value:’ending’, done:false} 
 hw.next() 
 // {value:undefined, done:true}


上面代码一共调用了4次next方法。
总结一下,调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。
value属性表示当前的内部状态的值,是yield语句后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
二、与Iterator接口的关系
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。

var myIterable = {}; 
 myIterable[Symbol.iterator] = function* () { 
 yield 1; 
 yield 2; 
 yield 3; 
 }; 
 […myIterable] // [1, 2, 3]


上面代码中,Generator 函数赋值给Symbol.iterator属性,从而使得myIterable对象具有了 Iterator 接口,可以被…运算符遍历了。
三、next()方法的参数
之前我们使用过next()函数,但是并没有传递任何参数,其实通过传参地一定的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值。也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为, 注意,由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。
我们可以看一段代码:

function* foo(x) { 
 var y = 2 * (yield (x + 1)); 
 var z = yield (y / 3); 
 return (x + y + z); 
 } 
 var a = foo(5); 
 a.next() // Object{value:6, done:false} 
 a.next() // Object{value:NaN, done:false} 
 a.next() // Object{value:NaN, done:true} 
 var b = foo(5); 
 b.next() // { value:6, done:false } 
 b.next(12) // { value:8, done:false } 
 b.next(13) // { value:42, done:true }


上面代码中,第二次运行next方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),除以 3 以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN。
如果向next方法提供参数,返回结果就完全不一样了。上面代码第一次调用b的next方法时,返回x+1的值6;第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。
四、for…of循环
for…of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。

function* foo() { 
 yield 1; 
 yield 2; 
 yield 3; 
 yield 4; 
 yield 5; 
 return 6; 
 } 
 for (let v of foo()) { 
 console.log(v);


}// 1 2 3 4 5
上面代码使用for…of循环,依次显示 5 个yield表达式的值。这里需要注意,一旦next方法的返回对象的done属性为true,for…of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for…of循环之中。
五、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 } 
 g.next() // { value: undefined, done: true }


上面代码中,遍历器对象g调用return方法后,返回值的value属性就是return方法的参数foo。并且,Generator 函数的遍历就终止了,返回值的done属性为true,以后再调用next方法,done属性总是返回true。如果return方法调用时,不提供参数,则返回值的value属性为undefined。
六、throw()
在调用throw()后同样会终止所有的yield执行,同时会抛出一个异常,需要通过try-catch来接收:

var g = function* () { 
 try { 
 yield; 
 } catch (e) { 
 console.log(‘内部捕获’, e); 
 } 
 }; 
 var i = g(); 
 i.next(); 
 try { 
 i.throw(‘a’); 
 i.throw(‘b’); 
 } catch (e) { 
 console.log(‘外部捕获’, e); 
 } 
 // 内部捕获 a 
 // 外部捕获 b


上面代码中,遍历器对象i连续抛出两个错误。第一个错误被 Generator 函数体内的catch语句捕获。i第二次抛出错误,由于 Generator 函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。
七、yield*
yield*用来将一个Generator放到另一个Generator函数中执行。

function* bar() { 
 yield ‘x’; 
 yield* foo(); 
 yield ‘y’; 
 } 
 // 等同于 
 function* bar() { 
 yield ‘x’; 
 yield ‘a’; 
 yield ‘b’; 
 yield ‘y’; 
 } 
 // 等同于 
 function* bar() { 
 yield ‘x’; 
 for (let v of foo()) { 
 yield v; 
 } 
 yield ‘y’; 
 } 
 for (let v of bar()){ 
 console.log(v); 
 } 
 // “x” // “a”// “b // “y” 
 八、运用场景 
 1、代替递归 
 斐波那契数列的实现: 
 function * fibonacci(seed1, seed2) { 
 while (true) { 
 yield (() => { 
 seed2 = seed2 + seed1; 
 seed1 = seed2 - seed1; 
 return seed2; 
 })(); 
 } 
 } 
 const fib = fibonacci(0, 1); 
 fib.next(); // {value: 1, done: false} 
 fib.next(); // {value: 2, done: false} 
 fib.next(); // {value: 3, done: false} 
 fib.next(); // {value: 5, done: false} 
 fib.next(); // {value: 8, done: false}


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

function* main() { 
 var result = yield request(“http://some.url“); 
 var resp = JSON.parse(result); 
 console.log(resp.value); 
 } 
 function request(url) { 
 makeAjaxCall(url, function(response){ 
 it.next(response); 
 }); 
 } 
 var it = main(); 
 it.next();


上面代码的main函数,就是通过 Ajax 操作获取数据。可以看到,除了多了一个yield,它几乎与同步操作的写法完全一样。注意,makeAjaxCall函数中的next方法,必须加上response参数,因为yield表达式,本身是没有值的,总是等于undefined。
逐行读取文本文件:

function * numbers() { 
 let file = new FileReader(“numbers.txt”); 
 try { 
 while(!file.eof) { 
 yield parseInt(file.readLine(), 10); 
 } 
 } finally { 
 file.close(); 
 } 
 }


3、控制流的管理
如一个多步操作非常耗时,采用回调的话:

step1(function (value1) { 
 step2(value1, function(value2) { 
 step3(value2, function(value3) { 
 step4(value3, function(value4) { 
 // Do something with value4 
 }); 
 }); 
 }); 
 }); 
 采用promise改写: 
 Promise.resolve(step1) 
 .then(step2) 
 .then(step3) 
 .then(step4) 
 .then(function (value4) { 
 // Do something with value4 
 }, function (error) { 
 // Handle any error from step1 through step4 
 }) 
 .done(); 
 而使用generator函数: 
 function* longRunningTask(value1) { 
 try { 
 var value2 = yield step1(value1); 
 var value3 = yield step2(value2); 
 var value4 = yield step3(value3); 
 var value5 = yield step4(value4); 
 // Do something with value4 
 } catch (e) { 
 // Handle any error from step1 through step4 
 } 
 }