一、什么是Generator 函数
1.1 语法
学习
Generator语法,你需要了解function*、yield、next三个基本概念。
function*用来声明一个函数是生成器函数,它比普通的函数声明多了一个*,*的位置比较随意可以挨着function关键字,也可以挨着函数名yield产出的意思,这个关键字只能出现在生成器函数体内,但是生成器中也可以没有yield关键字,函数遇到yield的时候会暂停,并把yield后面的表达式结果抛出去next作用是将代码的控制权交还给生成器函数
// 声明生成器函数 |
1.2 过程分析
// 分析一个简单例子 |
- 创建了
h对象,指向helloGenerator的句柄 - 第一次调用
next(),执行到"yield hello",暂缓执行,并返回了"hello" - 第二次调用
next(),继续上一次的执行,执行到"yield generator",暂缓执行,并返回了"generator"。 - 第三次调用
next(),直接执行return,并返回done:true,表明结束
经过上面的分析,
yield实际就是暂缓执行的标示,每执行一次next(),相当于指针移动到下一个yield位置

总结一下,Generator函数是ES6提供的一种异步编程解决方案。通过yield标识位和next()方法调用,实现函数的分段执行
1.3 yield 表达式
yield是Generator函数的暂缓执行的标识,对于yield只能配合Generator函数使用,在普通的函数中使用会报错
Generator函数中还有一种yield*这个表达方式
function* foo(){ |
当执行
yield*时,实际是遍历后面的Generator函数,等价于下面的写法:
|
注意:yield 后面只能适配Generator函数
二、Generator应用场景
2.1 异步操作的同步化表达
Generator函数的暂停执行的效果,意味着可以把异步操作写在yield表达式里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数
function* loadUI() { |
上面代码中,第一次调用
loadUI函数时,该函数不会执行,仅返回一个遍历器。下一次对该遍历器调用next方法,则会显示Loading界面,并且异步加载数据。等到数据加载完成,再一次使用next方法,则会隐藏Loading界面。可以看到,这种写法的好处是所有Loading界面的逻辑,都被封装在一个函数,按部就班非常清晰
- 通过
Generator函数部署Ajax操作,可以用同步的方式表达。
function* main() { |
2.2 控制流管理
// 异步函数 |
使用
Generator函数可以这样写
function * getData () { |
然后我们这样逐步执行
var g = getData() |
上面的代码,我们逐步调用遍历器的
next()方法,由于每一个next()方法返回值的value属性为一个Promise对象,所以我们为其添加then方法, 在then方法里面接着运行next方法挪移遍历器指针,直到Generator函数运行完成,实际上,这个过程我们不必手动完成,可以封装成一个简单的执行器
function run (gen) { |
run方法用来自动运行异步的Generator函数,其实就是一个递归的过程调用的过程。这样我们就不必手动执行Generator函数了。 有了run方法,我们只需要这样运行getData方法
run(getData) |
这样,我们就可以把异步操作封装到
Generator函数内部,使用run方法作为Generator函数的自执行器,来处理异步。其实我们不难发现,async/await方法相比于Generator处理异步的方式,有很多相似的地方,只不过 async/await 在语义化方面更加明显,同时async/await不需要我们手写执行器,其内部已经帮我们封装好了,这就是为什么说async/await是Generator函数处理异步的语法糖了
2.3 部署 Iterator 接口
利用
Generator函数,可以在任意对象上部署Iterator接口。
function* iterEntries(obj) { |
上述代码中,
myObj是一个普通对象,通过iterEntries函数,就有了Iterator接口。也就是说,可以在任意对象上部署next方法
// 下面是一个对数组部署 Iterator 接口的例子,尽管数组原生具有这个接口 |