JavaScript 中的 Generator 生成器

422 阅读2分钟

背景

我们一直认为在 JavaScript 中,所有函数执行时不会被任何事情打断。ES6 引入了一种新型的函数形式,称为 \color{red}{生成器}。生成器在执行过程中可以暂停自身,并可以主动恢复,在每次暂停/恢复循环都提供了一次双向传递数据的机会。生成器是遵循迭代器协议以及可迭代协议的。

使用方法

简单使用

生成器通过 \color{red}{yield} 中断程序执行,并返回 yield 语句右边的值。生成器调用后会生成一个迭代器,迭代器 next 方法可以传值会生成器中。

function *foo(){
    var a = yield 1;
    var b = yield 2;
    var c = yield 3;
    return {a,b,c}
}
var it = foo() 
it.next('a')
it.next('b')
it.next('c')
it.next('d')
/*
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: { a: 'b', b: 'c', c: 'd' }, done: true }
*/

语法规则

\color{red}{yield} 关键字将 next 传入的参数赋值给当前上下文。yield 是低运算优先级右结合的,当遇到左面的变量会抛出异常。

var a = yield 1;
//等价于
var a = blanck = 1;

//而下面这样是非法的
var a = 'hi' + yield 1;
//等价于
var a = 'hi' + blanck = 1;

yield*(yield委托)

yield*(yield委托),把生成器控制委托给迭代器,直到其耗尽。

function *foo(){
    yield *[1,2,3]
}
let it = foo()
it.next()   //{ value: 1, done: false }
it.next()   //{ value: 2, done: false }
it.next()   //{ value: 3, done: false }

生成器上附着的迭代器支持可选的 return(..) 和 throw(..) 方法。这两 种方法都有立即终止一个暂停的生成器的效果。

function *foo() { 
 yield 1; 
 yield 2; 
 yield 3; 
} 
var it = foo(); 
it.next(); // { value: 1, done: false } 
it.return( 42 ); // { value: 42, done: true } 
it.next(); // { value: undefined, done: true }

co (类 async,await)

TJ 大神著名的 \color{red}{co} 库,正是结合 Promise 与 Generator 的特性,实现了异步代码同步执行。
co 通过传入生成器,再将生成器的调用权放在 Promise 中,通过生成器 可以双向传递数据实现。

function co(gen){
    var it = gen();
    return new Promise((resolve, reject) => {
        onFufilled()
        function onFufilled(res){
            let ret = it.next(res);
            next(ret)
        }
        function onRejected(err){
            let ret = it.throw(err)
            next(ret)
        }
        function next(ret){
            if(ret.done) return resolve(ret.value);
            return ret.value.then(onFufilled, onRejected)
        }
    })
}
let itResult = co(function *foo(){
    let a = yield new Promise((resolve,reject)=>resolve(1))
    console.log(a)
    let b = yield new Promise((resolve,reject)=>resolve(2))
    console.log(b)
})
itResult.then(res=>console.log(res))
/*
1
2
undefined
*/

简单实现

基于 Facebook 的 \color{red}{regenerator} 工具实现简易版生成器模型。

function foo(){
    function nextState(v){
        switch(state){
            case 0:
                state++;
                return 42;
            case 1:
                state++;
                x = v;
                console.log(x);
                return undefined;
        }
    }
    var state = 0, x;
    return {
        next: function(v){
            var ret = nextState(v);
            return { value: ret, done: (state == 2)}
        }
    }
}
var it = foo();
it.next()   // {value: 42, done: false}
it.next(10) //10
            // {value: undefined, done: true}

参考资料

  • 你不知道的《javascript》
  • MDN
  • co 库