Generator函数

175 阅读3分钟

Generator函数干嘛的?

  Generator函数是一个遍历器对象生成函数,执行它就会生成一个遍历器对象。通过执行遍历器对象的next方法,就可以获取遍历器对象中的状态。

Generator函数跟普通函数差别

  1. function与函数名之间有个*
  2. 内部有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函数自执行的两种方法:

  1. Thunk函数
  2. 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;