2015年ES6的发布带来了一系列的JS新特性,Generator在其中属于一个比较重要的新特性,这里就详细地介绍一下。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
以这个最基本的Generator为例,他和普通函数有以下两点区别:一个是使用了function*,有点类似于我们在强类型语言中常见的指针;另一个是在函数内部有Yield关键字。
yield的作用在于暂停函数,并且返回yield右边的值。
yield 1+1 //返回2
yield 1 == 1 //返回true
我们知道了yield的作用,那么它在Generator中是有通过什么方式使用的呢?
Generator在创建时不会执行函数(再一次类比于指针,就像是我们只创建了一个指向该函数的指针但是没有执行),只有我们使用了*.next()之后才会开始执行,并且在下一个yield处停止,参考以下例子
function* test(){
console.log('hello');
yield ("i'm stopped");
console.log('world');
}
var g = test();
var yield_value = g.next();//{value:'i'm stopped', done:false}
g.next();
- 我们先是创建了一个test的实例,这时什么也没有发生
- 然后我们调用了next()语句,输出hello,之后在yield暂停,并且返回i’m stopped;给yield_value
- 接下来再次执行next()语句,输出world,程序结束(done参数表示函数是否已经执行完毕,这时的done为true)。
我们需要注意的是,yield返回了右边表达式的值,但是在函数内部将yield表达式直接赋值是不可行的,比如以下这个例子
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}
- 我们想要的结果应该和没有yield一样的,可是就像上面显示的一样,next函数yield只会将右边表达式的值传递给next,但是不能够传递给左边的表达式。
- 准确来说,yield会将next()里面的参数传递给左边的表达式。如果我们写成如下形式的话就可以让函数输出我们需要的结果了。
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
var i = a.next();
i = a.next(i.value);
i = a.next(i.value);
console.log(i.value);
进阶玩法,在不清楚有多少个yield的情况下可以以这种方式解决
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
var i = {value:0};
while (!(i = a.next(i.value)).done);
//不断保存之前yield的值再传递给下一个next直到函数结束(done为true)
console.log(i.value);//21
- 注意,yield只能在generator,即有*号的函数中使用,普通函数中使用会出错
- 在表达式中使用需要加上括号,不然会认为这是一个变量。
其他的应用方式可以参考ES6入门比较详细地介绍了各种属性和用法;但是我这里只是为了入门,一些高级技巧没有实际的使用场景的话很难记住,这里就不做介绍了。
Generator的功能
介绍完Generator,他到底有什么用呢?它作为ES6中协程的一个概念引进的。有了yield的帮助,我们可以自行定义语句执行顺序,协调好各个代码逻辑。我们这里使用它来解决同步编程中callback hell的问题,但是我们也要明白,generator不仅仅是解决callback而已,和promise相比,generator能够操控语句执行,顺带地解决了异步,而promise是专为同步而生的,他本质只是把callback hell转换成便于阅读的形式,而不是从根本改变代码的执行形式。
CO库使用Generator的特性来生成同步的函数。具体看下面的例子
co(function *(){
var rs = yield db.query('select url from xxx');
rs.forEach(rs){
var content = yield getUrl(rs.url);
...
}
})();
只要用CO将generator套起来,里面的代码不需要其他任何的改变,他就会使得代码等待yield右边表达式运行结束再继续,实现了同步编程又不需要改变代码结构。
有一些我们required的模块不支持同步,这时我们可以使用thunkify来转化一下使用
var thunkify = require('thunkify');
var fs = require('fs');
var read = thunkify(fs.readFile);
read('package.json', 'utf8')(function(err, str){
});
此外,对于GENERATOR我们还有一些扩展API的模块Yield可供我们使用
Yield