生成器函数

生成器(Generator)函数写成:

function* func () {}
`</pre>

其本质也是一个函数, 所以他具备普通函数所具有的所有特性, 除此以外, 他还具有以下特性
  1. 执行生成器函数后返回一个迭代器(Iterator)

  2. 生成器函数内部可以使用 yield 或 yield*, 函数执行到 yield 的时候会暂停执行, 并返回 yield 后面表达式的值, 通过迭代器的 next() 方法返回一个对象, 该对象由 value 和 done 属性组成, 其中, value 属性值为 yield 后面表达式的值, done 属性表示生成器的执行状态

  3. 迭代器 next() 方法的参数是 yield 表达式的值

  4. 当 next 指向最后一个 yield 的时候, 再次执行 next() 方法, 返回的对象中 done: true, 而 value 的值为 return 后面的表达式(默认为 undefined)

    例一

    `function test() {
      return 'b';
    }
    function* func() {
      var a = yield 'a';
    
      console.log('gen', a); // 'gen: undefined' - (如果 next 方法不带参数, yield 表达式返回 undefined)
      var b = yield test();
      console.log('gen': b); // 'gen: undefined'
    }
    
    var func1 = func();
    var a = func1.next();
    console.log('next: ' a); // 'next: {value: 'a', done: false}'
    var b = func1.next();
    console.log('next: ' b); // 'next: {value: 'b', done: false}'
    var c = func1.next();
    console.log('next: ' c); // 'next: {value: undefined, done: true}'
    `

    关于 yield*

    yield 暂停执行并返回后面表达式的值, 而 yield* 则将函数委托到另一个迭代器或可迭代对象

    arguments

    `> function* genFunc(){
    ... yield arguments;
    ... yield* arguments;
    ... }
    undefined
    > var gen = genFunc(1,2);
    undefined
    > gen.next()
    { value: { '0': 1, '1': 2 }, done: false }
    > gen.next()
    { value: 1, done: false }
    > gen.next()
    { value: 2, done: false }
    > gen.next()
    { value: undefined, done: true }
    `

    Generator

    `> function * gen1(){
    ... yield 2;
    ... yield 3;
    ... }
    undefined
    > function * gen2(){
    ... yield 1;
    ... yield* gen1();
    ... yield 4;
    ... }
    undefined
    > var g2 = gen2();
    undefined
    > g2.next()
    { value: 1, done: false }
    > g2.next()
    { value: 2, done: false }
    > g2.next()
    { value: 3, done: false }
    > g2.next()
    { value: 4, done: false }
    > g2.next()
    { value: undefined, done: true }
    `

    Thunk 函数

    在 co 的应用中, 为了能像同步代码那样书写异步代码, 比较多的使用方式是使用 thunk 函数(但不是唯一方式, 还可以是 Promise), 比如读取文件内容的异步函数 fs.readFile() 方法, 转化为 thunk 函数的方式如下

    `function readFile(path, encoding){
      return function(cb){
        fs.readFile(path, encoding, cb);
      }
    }
    `

    Thunk 函数具备以下两个要素
    1. 有且只有一个参数是 callback 的函数 
    2. callback 的第一个参数是 error

    使用 thunk 函数, 同时结合 co 我们就可以像书写同步代码一样书写异步代码

    `var co = require('co');
    var fs = require('fs');
    var Promise = require('es6-promise').Promise;
    
    function readFile(path, encoding){
      return function(cb) { // thunk function
        fs.readFile(path, encoding, cd);
      };
    }
    
    co(function* () { // 外部不可见, 但在 co 内部其实已经转化成promise.then().catch()...链式调用的行驶
      var a = yield readFile('a.txt', {encoding: 'utf-8'});
      console.log(a); // a
      var b = yield readFile('b.txt', {encoding: 'utf-8'});
      console.log(b); // b
      return yield Promise.resolve(a+b);
    }).then(function(val){
      console.log(val) // ab
    }).catch(function(error){
      console.log(error);
    })
    `

    有一个框架 thunkify 可以帮我们轻松实现以上步骤

    `var co = require('co');
    var thunkify = require('thunkify');
    var fs = requrie('fs');
    var Promise = require('es6-promise').Promise;
    
    var readFile = thunkify(fs.readFile);
    
    co(function* () { // 外部不可见, 但在 co 内部已经转化为 promise 的链式调用
      var a = yield readFile('a.txt', {encoding: 'utf-8'});
      console.log(a); // a
      var b = yield readFile('b.txt', {encoding: 'utf-8'});
      console.log(b); // b
      return yield Promise.resolve(a+b);
    }).then(function(val){
      console.log(val); // ab
    }).catch(function(err){
      console.log(err);
    })
    `

    对于 thunkify 框架的理解注释如下:

    `/**
     * Module dependencies
     */
    var assert = require('assert');
    
    /**
     * Expose `thunkify()`.
     */
    
    module.exports = thunkify;
    
    function thunkify(fn) {
      assert('function' == typeof fn, 'function required');
    
      // 返回一个包含 thunk 函数的函数, 返回的 thunk 函数用于执行yield, 而外层的这个函数用于给 thunk 函数传递其他参数
      return function() {
        var args = new Array(arguments.length);
        // 缓存上下文环境, 给 fn 提供执行环境
        var ctx = this;
    
        // 将参数类数组对象转为数组(实现方式略臃肿, 可以直接用Array.from)
        for (var i = 0; i < args.length; ++i){
          args[i] = argumetns[i];
        }
    
        // 真正的 thunk 函数(有且只有一个参数, 并且参数是第一个参数为 err 的 callback)
        return function(done) { // 返回 thunk 函数
          var called;
    
          // 将回调函数再包裹一层, 避免重复调用, 同时, 将包裹了的真正的回调函数 push 进参数数组(即将回调函数 done 作为最后一个参数传递给 fn)
          args.push(function(){  // 将回调函数 done 加到参数列表尾部, 形成 fn 需要的 [arg1, ..., done]
            if (called) return;
            called = true;
            done.apply(null, arguments); // 执行回调函数
          });
    
          try {
            // 在 ctx 环境执行 fn
            // 并将执行 thunkify 之后返回的函数的参数(含 done 回调) 传入, 类似于执行:
            // fs.readFile(path, {encoding: 'utf-8'}, done)
            fn.apply(ctx,args); // 类似 fn(arg1, .., done)
          } catch(err) {
            done(err);
          }
        }
      }
    }