ES6 Features系列:GeneratorFunction引见

一、媒介                           

第一次看koajs的示例时,发明该语句 function *(next){……………} ,这是啥啊?因而搜刮一下,本来这是就是ES6的新特征Generator Function(天生器函数)。

那什么是天生器函数呢?实在就相当于C#2.0中经由历程yield症结字完成的迭代器的天生器(细节有所不同),那末明白的症结就在迭代器和yield症结字两部分了。下面将尝试从表象动身,逐渐对天生器函数及利用它举行异步编程举行浅层的剖析明白。

二、表象——语法及基础运用                   

示例:

// 定义天生器函数
function *enumerable(msg){
  console.log(msg)
  var msg1 = yield msg + '  after '
  console.log(msg1)
  var msg2 = yield msg1 + ' after'
  try{
    var msg3 = yield msg2 + 'after'
    console.log('ok')
  }
  catch(e){
    console.log(e)
  }
  console.log(msg2 + ' over')
}

// 初始化迭代器
var enumerator = enumerable('hello')
var ret = enumerator.next() // 掌握台显现 hello,ret的值{value:'hello after',done:false}
ret =  enumerator.next('world') // 掌握台显现 world,ret的值{value:'world after',done:false}
ret = enumerator.next('game') // 掌握台显现game,ret的值{value:'game after',done:false}
// 抛出非常信息
ret = enumerator.throw(new Error('test')) // 掌握台显现new Error('test')信息,然后显现game over。ret的值为{done:true}

// for...of语句
enumerator = enumerable('hello')
for(ret of enumerator)
  console.log(JSON.stringify(ret));
// 掌握台顺次显现
// hello
// {value:'hello after',done:false}
// world
// {value:'world after',done:false}
// {value:'game after',done:false}
// game over
// {done:true}

1. 天生器语函数定义

function* test(){}
function * test(){}
function *test(){}
test = function* (){} 
test = function *(){}

一般函数增加*号后则成为了成为了天生器函数了。

Object.prototype.toString.call(test) // 显现[object GeneratorFunction]
  天生器函数的行动与一般函数并不相同,表现为以下3点:
  1. 经由历程new运算符或函数挪用的情势挪用天生器函数,均会返回一个天生器实例;
  2. 经由历程new运算符或函数挪用的情势挪用天生器函数,均不会立时实行函数体的代码;
  3. 必需挪用天生器实例的next要领才会实行天生器函数体的代码。

function *say(msg){
  console.log(msg)
}
var gen = say('hello world') // 没有显现hello world
console.log(Object.prototype.toString.call(gen)) // 显现[object Generator]
gen.next() // 显现hello world

2、 症结字yield——迭代器天生器

用于立时退出代码块并保存现场,当实行迭代器的next函数时,则能从退出点恢复现场并继续实行下去。下面有2点须要注重:
1. yield背面的表达式将作为迭代器next函数的返回值;
2. 迭代器next函数的入参将作为yield的返回值(有点像运算符)。
3、迭代器(Generator)
迭代器是一个具有 {value:{}, done:{Boolean}} next([])要领 和 {undefined} throw([*])要领 的对象,经由历程next函数不停实行以症结字yield支解的代码段,经由历程throw函数令yield支解的代码段抛出非常。

三、中心1——迭代器                     

迭代器更多的是指迭代器形式,迭代器形式是指经由历程一个名为迭代器的对象按肯定的划定规矩遍历鸠合元素,挪用者只需通知迭代器猎取下一个元素即可,而鸠合的范例、如何猎取元素等要素均由详细的迭代器自行处置惩罚。(又一次地关注点星散!)而且因为迭代器形式能够做到 按需实行/耽误实行 的效果,因而能下降遍历无穷序列时内存/栈溢出的题目,也能作为异步编程形式运用。
形式明白的注重点:
1. 迭代器每次进接见鸠合的一个元素,并由挪用者提议接见要求时迭代器才实行下一次接见操纵
2. “按肯定的划定规矩”,意味着不肯定遍历鸠合中一切的元素,而且划定规矩能够内聚到迭代器的详细完成上,也可经由历程战略形式外移到其他模块中;
3. “鸠合”,鸠合能够是一最先就已初始化好的有限序列鸠合(如[1,2,3,4,5,6,7]),也能够是按需天生的无穷序列鸠合(如1到无穷大)
4. “鸠合元素”,能够是整数鸠合、字符串鸠合等数据鸠合,也能够是函数等指令+数据的鸠合;
若触过C#、Java等服务端语句的朋侪应当对迭代器有肯定水平的相识,C#的IEnumrable、IEnumerator和Java的Iterable、Iterator就是跟迭代器相干的接口定义,继续上述接口的迭代器完成均能够经由历程foreach或for…in语句作轮回操纵。

那末这里有2点是要注重的:
1. 迭代器是指设想形式,跟详细的言语无关,因而一切言语都可依据该形式完成详细的迭代器;
2. foreach或for…in语句是语法层面的支撑,跟迭代器形式没有必然联系。(若语法层面不支撑,那函数式编程中的递归的效果是一样的,如果编译器/剖析器支撑尾递归则更好了,能够JS不支撑)
下面我们经由历程迭代器来完成Python中的range函数,并经由历程range函数建立一个超大的有限序列正整数鸠合(直接用数组的话绝有能够致使栈溢出哦!)。

// 迭代器组织函数
var RangeIterator = function(start,end,scan){
    this.start = arguments.length >= 2 ? start : 0    
    this.end = end == undefined ? start : end
    this.scan = scan || 1
    this.idx = this.start
}
// 向迭代器提议接见下一个元素的要求
// FF和ES6下迭代器接口范例定义了迭代器必需经由历程名为next的函数提议接见下一个元素的要求
RangeIterator.prototype.next = function(){
    if (this.idx > this.end) 
    if (!!StopIteration) {
         throw StopIteration
       }else{
          return void 0
       }

    var ret = this.idx
    this.idx += this.scan
    return ret
}
// Python中的range函数
var range = function(start, end, scan){
   var iterator = new RangeIterator(start, end, scan)
   return {
       // FF命令for...in语句挪用对象的迭代器的接口范例
        __iterator__: function(){
            return iterator
        },
       // 暴露迭代器的next函数
        next: function(){
            return iterator.next()
        },
        toString: function(){
            // 能够会致使栈溢出
            var array = []
            for (var i = this.next(); i != void 0; i = this.next())
                array.push(i)
            return array + ''
        }    
    }
}
var r = range(1, 100000000000000000000)
// FF下
// 参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_Generators#.E5.AE.9A.E4.B9.89.E8.87.AA.E5.AE.9A.E4.B9.89.E8.BF.AD.E4.BB.A3.E5.99.A8
for(var i in r)
  console.log(i) // 显现1到99999999999999999999
// 一切浏览器
for (var i = r.next(); i != void 0; i = r.next())
  console.log(i) // 显现1到99999999999999999999

因为JS是单线程运转,而且当UI线程被壅塞N秒后,浏览器会讯问是不是住手剧本的实行,但上述代码并不会因为序列过大形成栈溢出的题目。如果预先天生1到99999999999999999999或更大数字的数组,那很有能够形成stack overflow。那是因为迭代器本质为一状况机,而挪用next函数则是触发状况的转换,而状况机中统一时候用于寄存变量的存储空间牢固,并不会涌现无穷增进的状况。

四、中心2——yield症结字                  

回到症结字yield上了,实在yield症结字就是以一种更直观、便利的体式格局让我们建立用于遍历有限序列鸠合的迭代器,而yield则用于将天生器函数的代码切片作为有限序列鸠合的元素(元素的范例为指令+数据,而不仅仅是数据罢了)。下面我们一同看看yield症结字是如何对代码切片的吧!

// 定义天生器函数
function *enumerable(msg){
  console.log(msg)
  var msg1 = yield msg + '  after '
  console.log(msg1)
  var msg2 = yield msg1 + ' after'
  console.log(msg2 + ' over')
}

上述代码终究会被剖析为下面的代码:

var enumerable = function(msg){
  var state = -1

  return {
    next: function(val){
      switch(++state){
         case 0:
                  console.log(msg + ' after')
                  break
         case 1:
                  var msg1 = val
                  console.log(msg1 + ' after')
                  break
         case 2:
                  var msg2 = val
                  console.log(msg2 + ' over')
                  break
      }
    }
  }
}

(注重:上述仅仅简朴的剖析,更庞杂的状况(前提掌握、轮回、迭代、非常捕捉处置惩罚等)能够参考@赵劼的《人肉反编译运用症结字yield的要领》)

五、异步挪用中的运用                   

因为迭代器形式完成 耽误实行/按需实行,因而可作为一种异步编程形式来运用。

var iterator = getArticles('dummy.json')
// 最先实行
iterator.next()
// 异步使命模子
function getData(src){
  setTimeout(function(){
    iterator.next({tpl: 'tpl.html', name: 'fsjohnhuang'})
  }, 1000)
}
function getTpl(tpl){
  setTimeout(function(){
    iterator.next('hello ${name}')
  }, 3000)
}
// 同步使命
function render(data, tpl){
  return tpl.replace(/\$\{(\w+)\}/, function(){
    return data[arguments[1]] ==  void 0 ? arguments[0] : data[arguments[1]]
  })
}

// 主逻辑
function *getAritcles(src){
  console.log('begin')
  var data = yield getData(src)
  var tpl = yield getTpl(data.tpl)
  var res = render(data, tpl)
  console.log(rest)
}

主逻辑中异步挪用的写法与同步挪用的基础没差别了,爽了吧!但异步使命模子与天生器函数及其天生的迭代器耦合性太大,照样不太好用。下面我们经由历程完成了Promises/A+范例的Q来进一步解耦。

若实行引擎不支撑症结字yield,那末上述代码不就没法实行了吗?照样那句话,yield症结字实在就是语法糖,终究照样会被剖析为一个迭代器。因而我们自行完成一个迭代器也是能完成上述效果的,不过历程会烦琐许多(若如第2节的示例那样存在try…catch语句,就烦琐死了@~@),而且代码的整齐性、可保护性就端赖攻城狮来保证了。(语法糖从语法层面简化编程和保护难度,但明白底层的事情道理也十分重要哦!)

六、与Q连系                        

// 异步使命模子
function getData(src){
  var deferred = Q.defer()
  setTimeout(function(){
   defer.resolve({tpl: 'tpl.html', name: 'fsjohnhuang'})
  }, 1000)
  return deferred.promise
}
function getTpl(tpl){
  var deferred = Q.defer()
  setTimeout(function(){
   defer.resolve('hello ${name}')
  }, 3000)
  return deferred.promise
}
// 同步使命
function render(data, tpl){
  return tpl.replace(/\$\{(\w+)\}/, function(){
    return data[arguments[1]] ==  void 0 ? arguments[0] : data[arguments[1]]
  })
}

// 主逻辑
Q.async(function *(){
  console.log('begin')
  var data = yield getData('dummy.json')
  var tpl = yield getTpl(data.tpl)
  var res = render(data, tpl)
  console.log(rest)
})

暂未浏览Q的源代码,暂不作详细剖析。横竖API就这样用,呵呵!

七、与iPromise连系                    

iPromise是我开辟的一个Promises/A+的完全完成,浏览源码你会发明它继续了jQuery.Deferred1.5~2.1、jsDeferred、mmDeferred和Promises/A官网完成示例的精巧设想,而且从v0.0.6最先支撑ES6特征GeneratorFunction。运用示例以下:

var getData = function(dataSrc){
  return iPromise(function(r){
    setTimeout(function(){
        r(dataSrc + ' has loaded')
    }, 1000)
  })
}
var getTpl = function(tplSrc){
  return iPromise(function(r){
    setTimeout(function(){
        r(tplStr + ' has loaded')
    }, 2000)
  })
}
var render = function(data, tpl){
    throw new Error('OMG!')
}

iPromise(function *(dataSrc, tplSrc){
  try{
    var data = yield getData(dataSrc)
    var tpl = yield getTpl(tplSrc)
    render(data, tpl)
  }
  catch(e){
    console.log(e)
  }
  console.log('over!')
}, 'dummyData.json', 'dummyTpl.json')
/* 效果以下 */
// 守候1秒多显现 dummyData.json has loaded
// 守候2秒多显现 dummyTpl.json has loaded
// 显现 Error: OMG!
//     Stack trace:
//     test10/render/</<@file:///home/fsjohnhuang/repos/iPromise/test/v0.0.2.html:190:6
// 显现 over!

v0.6.0的中经由历程递返来完成,详细以下(https://github.com/fsjohnhuang/iPromise/blob/master/src/iPromise.js#L7…):

// FF下天生器函数的入参必需在建立迭代器时通报
// 若第一次挪用迭代器的next函数通报参数,则会报TypeError: attempt to send 第一个入参值 to newborn generator
var iterator = mixin.apply(null, toArray(arguments,1))
var next = function(){
  var deferred = iPromise()
  deferred.resolve.apply(deferred, arguments)

  return deferred.then(function(){
    var yieldReturn = iterator.next.apply(iterator, arguments)
     if(yieldReturn.done) throw Error('StopIteration')

     return yieldReturn.value
  }).then(next, function(e){
    iterator.throw(e)
  })
}
deferred.resolve()
deferred.then(next)

八、总结                          

Generator Function并非为异步编程而生,但能够将它连系Promise来完成优越的异步编程模子。本篇内容仅简朴引见Generator Function及相干的异步编程内容,如有马虎请列位斧正,感谢!

九、 参考                          

http://huangj.in/765
https://www.imququ.com/post/generator-function-in-es6.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/The_Iter…
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Stat…*
http://www.cnblogs.com/yangecnu/archive/2012/03/17/2402432.html
http://www.cnblogs.com/draem0507/p/3795189.html
http://blog.zhaojie.me/2010/06/code-for-fun-iterator-generator-yield-i…
http://blog.zhaojie.me/2010/06/code-for-fun-iterator-generator-yield-i…
http://blog.zhaojie.me/2010/07/why-java-sucks-and-csharp-rocks-6-yield…
如果您以为本文的内容风趣就扫一下吧!捐赠互勉!
  

    原文作者:肥仔John
    原文地址: https://segmentfault.com/a/1190000003017970
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞