简介
Generator
函数是ES6
提供的一种异步编程解决方案。执行Generator
函数会返回一个遍历器对象,依次遍历Generator
函数内部的每一个状态。
function关键字与函数名之间有一个星号。
函数体内部使用yield语句,定义不同的内部状态
1 | function* helloWorldGenerator() { |
1 | hw.next() |
第一次调用,执行Generator函数,直到遇到第一个yield为止,返回yield语句的值。
第二次调用,从上次停下的yield语句开始,执行到下一个yield或者return语句。
第三次调用,从上次停下的yield语句开始,执行到下一个yield或者return语句,done为true,表示遍历已经结束。
第四次调用,遍历已经结束,next方法返回value为undefined,同时done为true表示结束。
ES6没有规定星号写在哪个位置,所以下面的都能通过。
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· } // 普通写法
function*foo(x, y) { ··· }
yield语句
Generator
函数执行的暂停标志。
yield
后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行。(Lazy Evaluation
)
yield
和return
的区别在于,一个Generator
函数可以多次执行yield
语句,但是只能执行一次return
语句,遇到第一个return
语句之后,后续不会再执行。
另外,yield
语句不能用在普通函数中。
yield语句如果要放到一个表达式中,则必须放到圆括号中。
1 | var generator = function* () { |
与Iterator接口关系
任意一个对象的Symbol.iterator
方法,等于该对象的遍历器对象生成函数,调用该函数会返回该对象的一个遍历器对象。
1 | function* gen(){ |
next方法的参数
yield
语句的返回值是undefined
。next
方法可以带一个参数,被当做上一个yield
语句的返回值。
这一段感觉有点无法理解。看两段代码:
1 | function* gen() { |
教程中的例子比较好的说明了next参数的用途。
1 | function* foo(x) { |
上面代码中,第二次运行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。
注意,由于next方法的参数表示上一个yield语句的返回值,所以第一次使用next方法时,不能带有参数。V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。
第一次就使用next参数的情况,在Generator函数外再包一层。
1 | function wrapper(generatorFunction) { |
for…of循环
一旦返回值中的done参数为true的时候,则终止循环,并此次的value不做处理。
1 | function *foo() { |
不止是for...of
,扩展运算符(...
),解构赋值和Array.from
都是这样工作。
1 | function* numbers () { |
Generator.prototype.throw()
可以在函数体外抛出异常,并在函数体内捕捉。
1 | var g = function* () { |
然而,如果直接调用throw()
会直接抛错。
1 | var generator = function* () { |
这个问题已经提问出去了,回答似乎已经解决。另外,证明一点,stackoverflow
上的反应还是比segmentfault
要快很多,20分钟就解决了。
总体思路就是,g.next()
和g.throw()
都是同样的工作原理,只不过,g.throw()
执行之后会直接进入finally
模块,如果没有则直接退出。
1 | var generator = function* () { |
Generator.prototype.return()
可以返回给定的值,并且终结遍历Generator函数。
1 | function* gen() { |
如果Generator
函数内部有try...finally
代码块,那么return
方法会推迟到finally
代码块执行完再执行。
1 | function* numbers () { |
yield*语句
用于在一个Generator
函数中调用另一个Generator
函数。
相当于在外层Generator
函数中调用了一个for...of
循环。
1 | function* concat(iter1, iter2) { |
如果被代理的Generator
函数有return
语句,那么就可以向代理它的Generator
函数返回数据。
1 | function *foo() { |
yield*
命令可以很方便地取出嵌套数组的所有成员。
1 | function* iterTree(tree) { |
对象属性中的Generator函数
1 | let obj = { |
Generator函数的this
Generator函数返回的遍历器规定是Generator函数的实例,所以也继承了Generator函数的prototype
上的方法。但它不是普通的构造函数,所以返回的不是this
对象。
1 | function* g() {} |
如果必须要把Generator函数当做正常的构造函数来用,则可以生成一个空对象,然后使用bind
方法绑定到Generator函数,这样,调用Generator函数之后空对象就包含了函数定义的属性。
1 | function* F(){ |
应用
异步操作的同步化表达
通过Generator函数逐行读取文本文件。
1 | function* numbers() { |
控制流管理
回调函数写法。
1 | step1(function (value1) { |
进阶。Promise
应用。
1 | Q.fcall(step1) |
高阶。Generator函数。
1 | function* longRunningTask() { |
如果要任务A和任务B都执行完才执行任务C,则如下:
1 | function* parallelDownloads() { |
部署iterator接口
可以利用Generator函数在任意对象上部署iterator接口。
1 | function* iterEntries(obj) { |