在学习ES6的过程中,个人认为Generator稍微会有些难以理解,其实Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。
我们从表面上看,Generator函数是一个普通函数,但是有两个特征:
1. function关键字与函数名之间有一个星号;
2. 函数体内部使用yield语句,定义不同的内部状态(yield语句在英语里的意思就是“产出”)。
这里推荐给大家一个在线的测试工具,方便我们学习测试使用:
http://www.es6fiddle.net
我们来看一段简单的使用代码:
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
console.log(JSON.stringify(hw.next()));
console.log(JSON.stringify(hw.next()));
console.log(JSON.stringify(hw.next()));
上面代码中,我们定义了一个hello world的生成函数,从代码上看出,generator的语法定义主要有一个* 号,外加yield语法。
当我们调用helloWorldGenerator(), 并没有真正返回给我们加过,而是在pending的状态,只有当我们调用了next()语法之后,才看到打印结果。
另外需要注意,yield语句不能用在普通函数中,否则会报错。
这就是generator的基本使用。
使用Iterator遍历Generator函数:
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
next方法的参数
之前我们使用过next()函数,但是并没有传递任何参数,其实通过传参地一定的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值。也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为, 我们可以看一段代码:
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}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
对于yield方法,如果我们没有传递一个参数在next()方法中,yield的返回会是undefined, 这也就是为什么我们会看到value: NaN的结果。
再来看看传递参数后的结果, 首先我们一定要先传递一个参数,否则第一次运行x参数会报错误。
所以当第一次运行next()时候会看到 x=1 , 也就是5+1=6.
当第二次运行next(12), 传递一个参数12, 这个12会赋值给(yield (x + 1)), 也就是y的数值此时已经变为2 * 12 ,也就是24,在执行24/3, 就是第二次运行的结果:8 。
同理,第三次运行next(13), 传递一个参数13, 这个13会赋值给yield (y / 3), 此时的z也就等于13, 所以最后一次next的运行结果为13+24+5=42.
通过上面这个例子,大家可以简单理解其原理。
for …. of循环遍历Generator函数
其实遍历Generator函数的方法有很有,for… of就是其中一个,我们看下代码就明白了:
function *foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
常见的应用场景:
如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
采用Promise改写上面的代码。
Q.fcall(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.done();
Generator函数可以进一步改善代码运行流程:
function* longRunningTask() {
try {
var value1 = yield step1();
var value2 = yield step2(value1);
var value3 = yield step3(value2);
var value4 = yield step4(value3);
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
}
}
scheduler(longRunningTask());
function scheduler(task) {
setTimeout(function() {
var taskObj = task.next(task.value);
// 如果Generator函数未结束,就继续调用
if (!taskObj.done) {
task.value = taskObj.value
scheduler(task);
}
}, 0);
}