【javascipt】Generator函数(生成器)

key: Generator函数是ES6提供的一种异步编程解决方案

Generator函数和普通函数就有两个区别: 

        一是  function命令与函数名之间有一个星号 *   

        二是 函数体内有yield语句 定义不同的内部状态

----------------------------------------------------------------------------------

Generator函数返回的是一个遍历器对象。

为什么,请看下面的代码: 

    var g = gen()  

    g[Symbol.iterator] () ===g

MDN-Symbol.iterator   and  Ruanyf-ES6-Guide Iterator

----------------------------------------------------------------------------------

我们从一个例子开始:


function * gen () {
console. log( 'started')
console. log( yield 1)
yield 2
yield 3
}


    可以这样理解yield只是一个暂停状态的标记。

    我们先获取这个遍历对象,var g = gen() 

     g.next()第一次函数开始执行,遇到yield所在的行就停止了,所以这一次是输出了一个'started'. 但是我们知道.next()返回一个对象,对象的value 就是第一个暂停标记的yiled 后面表达式的值。 这一步的g.next()应该是{ value: 1, done: false }

     g.next()第二次执行,我们传递一个参数,g.next('abc') ,这时候我们先不讨论参数,只是讨论暂停状态的标记是yield 2, 这时候执行的代码是 console.log(yield 1), 但是这里我们传递了一个参数, 这个参数是上一个暂停标记(yield 表达式,请注意是整个yield表达式,也就是yield+expression)的值。所以这个yield 1 最后的值是'abc', 这时候输出的是一个 'abc'。 这里其实有点绕,实际上就是next(value),  这个value是上一个暂停表达式的值。

----------------------------------------------------------------------------------

    前面我们说了Generator是ES6的一个异步解决方案,但是我们看了前面的例子,都是以同步的语法和书序来执行完。到底generator是怎么帮助我们来处理异步方法呢?

    没错,就是通过next(),next()方法可以传递一个参数,这个参数就是上一个yield 表达式的值。



function * gen ( a) {
var x = yield a + 1
var y = yield x * 2
return y
}

    var g = gen(3)

    我们运行几次的结果是这样的:

        g.next()  // {value: 4, done: false}              

        g.next() // {value: NaN, done: false}  这一步因为我们没有给上一个 (yield 表达式)赋值,如果我们这样写,g.next(5),这样上一个 yield a+1 的值就是 5,x的值就为5, 这一步.next()的value就是5*2为0。 但是这个参数我们是不能写死的,因为这样没有任何的实际意义。我们可以将上一次的next()的value给保存下来,然后作为参数传递给下个next()。

    我们来写一个简要的流程控制器,专门处理这一串流程:


function exceuteGen( generatorFunc) {
let g = generatorFunc
let value
while( true) {
var temp = g. next( value)
if (! temp. done) {
value = temp. value
} else {
return value
break
}
}

}

var g = gen( 3)
console. log( '最后的结果是 ' + exceuteGen( g))

    我们来看gen, 里面y是依赖x的,我们以同步的方式去书写这两段代码,最后用一个执行器就完成了异步操作,如果不去写执行器,是不是觉得这样写比callback 和 promise都好的多,个人觉得是的。 callback一堆回调,promise一堆then的情况都比较繁琐,用generator要简洁的多【但是好多场景是generator 和 promise搭配一起使用】。

----------------------------------------------------------------------------------

    上面我们举得例子是串行,但是有的情况是需要并行的,比如我们要处理一个汇总数据,需要得到学生数据 和 书籍数据,两个对应方法是 getStudents() 和 getBooks()。 同时去获取这两个数据,然后基于这两个数据去执行下一步骤,我们可以这样写:


function* parallelTask () {
let [ students, books] = yield [
getStudents(),
getBooks()
]
console. log( students, books)
}

    

    //执行器的第二个next传递的参数格式请注意,要可迭代。传入数组或者Set【map请你自己去尝试】,不然会报错:(intermediate value) is not iterable

----------------------------------------------------------------------------------

    用Generator处理异步的好处是什么,通过上面的例子我们也能够看出来,是以看起来同步的方式去书写代码,然后用一个执行器去隐藏了异步的一些数据传递。这样我们以更加自然的方式去书写我们的代码。

    一般我们都会用一些库比如co,它们帮助我们写好了执行器,我们只需要定义这个generator函数即可。

    co模块返回一个promise对象。

    查看co的源码,其实可以发现,原理和我们上面写的generator函数执行器是一样的,我理解就是递归(可能不准确),不过co模块做了其他的优化。

    递归实现执行器的基本因素是什么?就是每一次传递的参数个数是一样的,我们默认回调里面传给下一个回调的参数是一个[next()的参数默认一个]这样我们写执行器的时候,就直接传递了,但是如果我们的回调参数不只一个,多个嵌套回调,甚至回调参数个数不是规律递增递减,这样的逻辑我们根本不能写一个自动执行器。所以co模块将传入的参数默认转换成promise对象,promise的fullfilled callback function只能接受一个参数,我想这就是为co模块编写自动执行器的一个前提条件



----------------------------------------------------------------------------------


async 和 generator类似,是为了异步操作变得更加简单,


var asyncReadFile = async function() {
var f1 = await readFile( '/etc/fstab')
var f2 = await readFile( '/etc/shells')
console. log( f1. toString())
console. log( f2. toString())
}


和generator比起来好处在于,async函数内置了执行器,我们不用像generator一样,额外的要附加一个执行器, async函数返回的是一个promise对象。



参考资料: generator -ruanyf 

                https://davidwalsh.name/es6-generators

                co -source code




猜你喜欢

转载自blog.csdn.net/a5534789/article/details/79749171
今日推荐