Generator函数

迭代器

有next方法,执行返回结果对象({value:'XX', done:'XX'})

// ES5实现迭代器
function createIterator(items) {
    var i = 0;
    return {
        next: function() {
            var done = i >= items.length;
            var value = !done ? items[i++] : undefined;
            return {
                done: done,
                value: value
            };
        }
    };
}

var iterator = createIterator([1, 2, 3]);

iterator.next(); // {value:1,done:false}
iterator.next(); // {value:2,done:false}
iterator.next(); // {value:3,done:false}
iterator.next(); // {value:undefined,done:true}

可迭代协议

[Symbol.iterator]属性
内置可迭代对象 String Array Map Set等

迭代器协议

有next方法,执行后返回一个对象{value:'XX', done:'XX'}

生成器

Generator函数(生成器)

ES6异步编程解决方案,在执行处暂停,又能从暂停处继续执行

  • 声明:通过function*声明
  • 返回值:符合可迭代协议和迭代器协议的生成器对象

yield

  • 只能出现在Generator函数
  • 用来暂停和恢复生成器函数

生成器对象

生成器对象原型上有三个方法

  • next(param)
  • return(param)
  • throw(param)
next
  • 执行
    遇yield暂停,将紧跟yield表达式的值作为返回的对象的value
    没有yield,一直执行到return,将return的值作为返回的对象的value
    没有return,将undefined作为返回的对象的value
  • 参数
    next方法可以带一个参数,该参数会被当做上一个yield表达式的返回值

代码分析:

function* createIterator() {
    let first = yield 1;
    let second = yield first + 2;
    yield second + 3;
}
let iterator = createIterator();

iterator.next(); // {value:1, done:false}
iterator.next(4); // {value: 6, done: false}
iterator.next(5); // {value: 8, done: false}
iterator.next(); // {value: undefined, done: true}

执行过程:

  1. 遇到yield暂停,返回yield表达式后的1作为返回对象的value,执行还未结束,结果为{value:1, done:false}
  2. 因为第一步执行到yield就已经暂停了,first还没有被赋值。第二次next执行的时候,first才开始赋值,因为传入了4,替换掉第一步中的1赋值给了first,first是4。返回yield后表达式计算的结果6作为返回对象的value,所以结果为{value: 6, done: false}
  3. 同第二步的过程,yield后表达式计算结果为8,虽然后面没有代码了,但是相当于有一个默认的return undefined,所以还没有结束,因此返回{value: 8, done: false}
  4. return undefined,代码执行结束,结果为{value: undefined, done: true}
return(param)

给定param值终结遍历器,param默认为undefined

function* createIterator() {
    yield 1;
    yield 2;
    yield 3;
}
let iterator = createIterator();

iterator.next();  // {value:1, done:false}
iterator.return(); // {value:undefined, done:true}
iterator.next();  // {value:undefined, done:true}
throw(param)

让生成器对象内部抛出错误

function* createIterator() {
    let first = yield 1;
    let second;
    try {
        second = yield first + 2;
    } catch (e) {
        second = 6;
    }
    yield second + 3;
}
let iterator = createIterator();

iterator.next(); // {value:1, done:false}
iterator.next(10); // {value:12, done:false}
iterator.throw(new Error("error")); // {value:9, done:false}
iterator.next(); // {value:undefined, done:true}
  1. 遇到yield暂停,返回yield表达式后的1作为返回对象的value,执行还未结束,结果为{value:1, done:false}
  2. 返回{value:12, done:false}
  3. iterator.throw(new Error("error"))会进入try{}catch{}中的catch,second赋值为6,继续往下,遇到yield才会停止,返回{value:9, done:false}
  4. 返回{value:undefined, done:true}

yield* 生成器函数/可迭代对象

  • 委托给其他可迭代对象
  • 作用:复用生成器

代码分析

function* generator1() {
  yield 1;
  yield 2;
}

function* generator2() {
  yield 100;
  yield* generator1(); // 控制权交出,进入generator1
  yield 200;
}

let g2 = generator2();
g2.next(); // {value:100, done:false}
g2.next(); // {value:1, done:false}
g2.next(); // {value:2, done:false}
g2.next(); // {value:200, done:false}
g2.next(); // {value:undefined, done:true}

Generator函数的实现原理

协程

  • 一个线程存在多个协程,但同时只能执行一个
  • Generator函数时协程在ES6的实现
  • yield挂起一个协程(交给其他协程),next唤醒协程

Generator函数应用

需求:按照顺序打印文件
直接用回调实现需要多层嵌套
使用Generator函数实现如下:

function* readFilesByGenerator() {
    const fs = require("fs");
    const files = [
        "/Users/kitty/testgenerator/1.json",
        "/Users/kitty/testgenerator/2.json",
        "/Users/kitty/testgenerator/3.json"
    ];
    let fileStr = "";
    function readFile(filename) {
        fs.readFile(filename, function(err, data) {
            console.log(data.toString());
            f.next(data.toString());
        });
    }
    yield readFile(files[0]);
    yield readFile(files[1]);
    yield readFile(files[2]);
}
// 调用
const f = readFilesByGenerator();
f.next()

但是此种方法在readFilesByGenerator函数内部用到了在外面定义的生成器对象f,耦合度太高。

thunk函数

  • 求值策略

    • 传值调用

      • 以sum(x+1,x+2)为例,先计算x+1和x+2的值,再传入sum函数进行计算
    • 传名调用

      • 以sum(x+1,x+2)为例,等到sum函数中用到传入的参数时,再去计算x+1和x+2的值
  • thunk函数是传名调用的实现方式之一
  • 可以实现自动执行Generator函数
const fs = require("fs");
const Thunk = function(fn) {
  return function(...args) {
    return function(callback) {
      return fn.call(this, ...args, callback);
    };
  };
};
const readFileThunk = Thunk(fs.readFile);

function run(fn) {
  var gen = fn();
  function next(err, data) {
    var result = gen.next(data);
    if (result.done) return;
    result.value(next);
  }
  next();
}

const g = function*() {
  const s1 = yield readFileThunk("/Users/kitty/testgenerator/1.json");
  console.log(s1.toString());
  const s2 = yield readFileThunk("/Users/kitty/testgenerator/2.json");
  console.log(s2.toString());
  const s3 = yield readFileThunk("/Users/kitty/testgenerator/3.json");
  console.log(s3.toString());
};

run(g);

CSep27
37 声望1 粉丝

学习中...整理中...