Generator函数干嘛的?
Generator函数是一个遍历器对象生成函数,执行它就会生成一个遍历器对象。通过执行遍历器对象的next方法,就可以获取遍历器对象中的状态。
Generator函数跟普通函数差别
- function与函数名之间有个*
- 内部有yield表达式,定义不同的状态
yield表达式与next方法
遍历器对象调用next方法,会指向下一个内部的状态,这就需要一个暂停标志位来表示执行的到的位置,而yield表达式就是这个暂停标志。
next方法调用过程:
1. 遇到yield表达式,就停止执行,并将yield表达式的值作为返回对象的value值,返回对象格式如下:
{
value: val, // 任意值
done: true // 或者 false
}
2. 下一次再调用next方法,继续执行,直到遇到下一个yield表达式;
3. 如果有没有遇到yield表达式,就一直运行到函数结束,有return语句,则将return语句后面的表达式的值作为返回对象的value值,不然就返回undefined。
yield和yield*差别
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
// 手动遍历 foo()
for (let i of foo()) {
console.log(i);
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// x
// a
// b
// y
上面的例子中foo和bar都是Generator函数,如果我们想要在bar中调用foo这个Generator函数,就要手动去遍历它。如果bar中嵌套了多个Generator函数,那就需要多次手动调用,这样就会很麻烦。而有yield* 就可以帮我们做到在一个Generator函数自动去执行另一个Generator函数。yield*后面的Generator函数(没有return语句时),等同于在Generator函数内部,部署一个for...of循环。
上面的例子bar函数就可以优化如下:
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
next方法和传值
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 }
上面的代码中,遍历器对象a的next方法第二次运行时,返回的value是NaN,是因为y=2*undefined=NaN导致的,也就是因为yield表达式本身是不会返回值的,只会返回undefined。
我们可以通过next方法对yield表达式进行传值。如遍历器对象b第二次调用next方法时,就将上一次yield表达式的返回结果赋值成了12,所以y=2 * 12 =24,所以第二次next运行的value为24/3=8。同理第三次调用next方法的是叫,将上一次yield表达式赋值成了13,所以z=13,所以第三次next运行的value为5+24+13=42。
Generator函数自执行
单纯的Generator函数的用途其实不大,因为我们需要先执行Generator函数,再一次次地调用next方法,而最大的问题还是yield表达式后面异步操作的值,需要通过next方法进行回传。
自执行的关键就是自动控制Generator函数的流程,接收和交还程序的执行权。回调函数和Promise都可以做到。
Generator函数自执行的两种方法:
- Thunk函数
- co模块核心代码
function run(gen) {
var gen = gen();
function next(data) {
var result = gen.next(data);
if (result.done) return;
if (isPromise(result.value)) {
result.value.then(function(data) {
next(data);
});
} else {
result.value(next)
}
}
next();
}
function isPromise(obj) {
return 'function' == typeof obj.then;
}
module.exports = run;