灏天阁

Js 中的生成器函数

· Yin灏
function* fn() {
    console.log(1);
    //暂停!
    yield;
    //调用next方法继续执行
    console.log(2);
}
var iter = fn();
iter.next(); //1
iter.next(); //2
  1. 函数生成器特点是函数名前面有一个 *

  2. 通过调用函数生成一个控制器

  3. 调用 next() 方法开始执行函数,生成器函数的执行,从调用 next() 函数开始

  4. 遇到 yield 函数将暂停

  5. 再次调用 next() 继续执行函数

消息传递

除了暂停和继续执行外,生成器同时支持传值。

function* fn() {
    var a = yield 'hello';
    yield;
    console.log(a); 
}
var iter = fn();
var res = iter.next();
console.log(res.value); //hello
iter.next(2); // 这里传值了 a = 2,如果不传值 a = undefined
iter.next(); //2

可以看到,yield 后面有一个字符串,在第一次调用 next 时,暂停在这里且返回给了 iter.next()

而暂停的地方是一个赋值语句,需要一个变量给 a,于是 next() 方法中传了一个参数 2 替换了 yield,最后打印 a 得到了 2。

异步应用

通过 yield 来实现异步控制流程:

function fn(a, b) {
    //假设这是一个ajax请求
    ajax('url' + a + b, function(data) {
        //数据请求到会执行it.next
        it.next(data);
    });
}
//这里是函数生成器
function* g() {
    //当异步操作完毕 yield 会得到值
    //这里会自动继续执行
    var text = yield fn(a, b);
    console.log(text);
}
var it = g();
it.next();

yield + promise

function foo(x) {
    // return 和 yield 作用相同,将返回值添加到了 { value: 返回值, done:false }
    return request('url' + x);
}
//等待promise决议值返回
function* fn() {
    var text = yield foo(1);
}
var it = fn();
//返回一个promise
var p = it.next().value;
//对promise处理
p.then(function(text) {
    //这里继续执行生成器
    it.next(text);
})

封装

可以将上面的 yield+promise 进行封装,得到下面的函数:

function run(gen) {
    //获取除了生成器本身以外的参数
    var args = [].slice.call(arguments, 1),
        it;
    //it = main()
    it = gen.apply(this, args);
    return Promise.resolve().then(function handleNext(value) {
        //第一次启动无value
        var next = it.next(value);
        return (function handleResult(next) {
            //执行完毕返回
            if (next.done) {
                return next.value;
            } else {
                //如果还有就决议next.value传给handleNext
                return Promise.resolve(next.value).then(handleNext, function(err) {});
            }
        })(next);
    });
}
//这是一个函数生成器
function* main() {
    //...
};
//该调用会自动异步运行直到结束
run(main);

如果有两个异步操作,获取到返回的两个数据后,再进行第三个异步操作,可以这么做:

function foo() {
    var p1 = request('url1'),
        p2 = request('url2');
    //每一个request异步请求完成后会自动解除yield
    var r1 = yield p1,
        r2 = yield p2;
    var r3 = yield request('url3' + r1 + r2);
    console.log(r3);
}

- Book Lists -