# 13. Generator函数

Generator函数是ES6提供的一种异步编程解决方案,Generator函数是一个普通函数,但又两个特征

  • function与函数名之间有* 星号
  • 函数内部使用yield表达式,定义不同的内部状态 (yield产出)
// Generator 函数执行会返回一个Iterator对象,可以用for of遍历,执行next() 会依次返回对应的状态值
// Generator 函数定义
function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
}
// Generator函数执行,也是和普通函数一样()
var hw = helloWorldGenerator()

hw.next() // {value: "hello", done: false}
hw.next() // {value: "world", done: false}
hw.next() // {value: "ending", done: true}
hw.next() // {value: undefined, done: true}
hw.next() // {value: undefined, done: true}

# 简介

ES6没有规定,function与函数之间的星号,写在哪个位置,下面4种写法都能通过

function * foo(x, y) { ... }
function *foo(x, y) { ... }
function* foo(x, y) { ... }   // 一般采用这种写法
function*foo(x, y) { ... }

# yield表达式

由于Generator函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以提供了一个可以暂停执行的函数,yield表达式就是暂停标志。

遍历器对象的next方法允许逻辑如下

  • 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的表达式的值,作为返回的value值,
  • 下一次调用next方法时,继续执行,直到遇到下一个yield表达式
  • 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
  • 如果该函数没有return语句,则返回的对象的value属性值为undefined。
// 只有调用next方法,内部指针指向该语句时才会执行。
function* gen() {
    yield 123 + 456
}
// yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值。


// Generator函数如果没有yield,就会变成一个暂缓执行函数。有next才会执行。
function* f() {
  console.log('执行了!')
}
var generator = f();

setTimeout(function () {
  generator.next()
}, 2000);

# yield注意事项

  • yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。
(function (){
  yield 1;
})()
// Uncaught SyntaxError: Unexpected number

// yield不能再普通函数里面使用的另一个例子:使用yield遍历多维嵌套数组里的每一个元素
// var arr = [1, [[2, 3]], 4], [5, 6]]
// forEach参数是普通函数,普通函数里面包含yield,会出错。
var arr = [1, [[2, 3], 4], [5, 6]];

var flat = function* (a) {
  a.forEach(function (item) {
    if (typeof item !== 'number') {
      yield* flat(item);   // 递归  
    } else {
      yield item;
    }
  });
};

for (var f of flat(arr)){
  console.log(f);
}

// 需要将forEach换位for
var arr = [1, [[2, 3], 4], [5, 6]]

var flat = function* (a) {
    for (let i = 0; i < a.length; i++) {
        if (typeof a[i]  !== 'number') {
            yield* flat(a[i])
        } else {
            yield a[i]
        }
    }
}
for (let f of flat(arr)) {
    console.log(f)
}
// 1 2 3 4 5 6 
  • yield表达式如果在另一个表达式里面,必须放到圆括号里面
function* demo() {
  console.log('Hello' + yield); // SyntaxError
  console.log('Hello' + yield 123); // SyntaxError

  console.log('Hello' + (yield)); // OK
  console.log('Hello' + (yield 123)); // OK
}
let k = demo()
k.next()
// { value: undefined, done: false}
k.next()
// { value: 123, done: false}
k.next()
// {value: undefined, done: true}
  • yield 表达式做函数参数,或者放在赋值表达式右侧可以不加括号
function* demo() {
  foo(yield 'a', yield 'b'); // OK
  let input = yield; // OK
}

# Generator函数与Iterator接口(Symbol.iterator)关系

var myIterator = {}

myIterator[Symbol.iterator] = function* () {
    yield 1
    yield 2
    yield 3
}
[...myIterator]   // [1, 2, 3]


function* gen() {
    // some code
}
var g = gen()
g[Symbol.iterator]() === g
// true
// gen是一个 Generator 函数,调用它会生成一个遍历器对象g。它的Symbol.iterator属性,也是一个遍历器对象生成函数,执行后返回它自己。

# next 方法的参数

yield表达式没有返回值,或者说总返回undefined,next可以跟一个参数,作为上一个yield表达式的返回值

// 示例1:
function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
  }
}
var g = f();

g.next() // 0
g.next() // 1
g.next() // 2
g.next(true) // 0    注意for循环执行顺序,以及yield执行时会卡住

// 示例2  注意yield表达式的返回值,如果next没指定参数,返回undefined, 
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 }

// 示例3:next向函数内部输入值得例子
function* dataConsumer() {
  console.log('Started');
  console.log(`1. ${yield}`);
  console.log(`2. ${yield}`);
  return 'result';
}

let genObj = dataConsumer();

genObj.next()
// Started
// {value: undefined, done: false}

genObj.next('a')
// 1. a
// {value: undefined, done: false}

genObj.next('b')
// 2. b
// {value: 'result', done: true}

# for...of循环

用for...of来遍历,会自动执行.next()直到遇到 done:true,且不会返回最后的值。

function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
}
var hw = helloWorldGenerator()
for (let k of hw) {
    console.log(k)
}
// hello
// world

function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}
// 扩展运算符
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解构赋值
let [x, y] = numbers();
x // 1
y // 2
// for...of 循环
for (let n of numbers()) {
  console.log(n)
}
// 1
// 2

# Generator.prototype.throw()

Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。和next类似,可以向内部抛一个错误

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('内部捕获', e);
  }
};
var i = g();
i.next();  // 卡在yield
try {
  i.throw('a'); // 继续执行,并抛出一个错误,错误被内部捕获,可以继续向下执行
  i.throw('b'); // 执行完成后,Generator函数不能再捕获错误了,外部可以捕获。
} catch (e) {
  console.log('外部捕获', e);
}
// 内部捕获 a  
// 外部捕获 b


// 不用try...catch是捕获不到错误的,就会被外部捕获
var g = function* () {
  while (true) {
    yield;
    console.log('内部捕获', e);
  }
};
var i = g();
i.next();
try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕获', e);
}
// 外部捕获 a


// g.throw抛出错误以后,没有任何try...catch代码块可以捕获这个错误,导致程序报错,中断执行。
var gen = function* gen(){
  yield console.log('hello');
  yield console.log('world');
}
var g = gen();
g.next();
g.throw();
// hello
// Uncaught undefined


// 需要至少执行一个next,才能内部捕获到错误。
function* gen() {
  try {
    yield 1;
  } catch (e) {
    console.log('内部捕获');
  }
}
var g = gen();
g.throw(1);
// Uncaught 1


// g.throw 捕获后,会有类似next()的效果。继续向下执行
var gen = function* gen(){
  try {
    yield console.log('a');
  } catch (e) {
    // ...
  }
  yield console.log('b');
  yield console.log('c');
}
var g = gen();
g.next() // a
g.throw() // b
g.next() // c


// 如果错误在generator函数内部产生,内部如果不捕获,外部也可以捕获到
// 并且generator执行过程中抛出错误,且内部不捕获,就不会继续执行下去了。
// 就不会再执行下去了。如果此后还调用next方法,将返回一个value属性等于undefined、done属性等于true的对象,
// 即 JavaScript 引擎认为这个 Generator 已经运行结束了。
function* foo() {
  var x = yield 3;
  var y = x.toUpperCase();
  yield y;
}
var it = foo();
it.next(); // { value:3, done:false }
try {
  it.next(42);
} catch (err) {
  console.log(err);
}
// TypeError: x.toUpperCase is not a function
    // at foo (<anonymous>:3:13)
    // at foo.next (<anonymous>)
    // at <anonymous>:9:6
it.next()  // {value: undefined, done: true}


// 第二个例子,内部不捕获,就会结束并改变态为true
function* g() {
  yield 1;
  console.log('throwing an exception');
  throw new Error('generator broke!');
  yield 2;
  yield 3;
}

function log(generator) {
  var v;
  console.log('starting generator');
  try {
    v = generator.next();
    console.log('第一次运行next方法', v);
  } catch (err) {
    console.log('捕捉错误', v);
  }
  try {
    v = generator.next();
    console.log('第二次运行next方法', v);
  } catch (err) {
    console.log('捕捉错误', v);
  }
  try {
    v = generator.next();
    console.log('第三次运行next方法', v);
  } catch (err) {
    console.log('捕捉错误', v);
  }
  console.log('caller done');
}

log(g());
// starting generator
// 第一次运行next方法 { value: 1, done: false }
// throwing an exception
// 捕捉错误 { value: 1, done: false }
// 第三次运行next方法 { value: undefined, done: true }
// caller done

# Generator.prototype.return()

Generator 函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历 Generator 函数。

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


// 例子2  g.return() 如果没有传参,就会返回undefined
function* gen() {
  yield 1;
  yield 2;
  yield 3;
}
var g = gen();
g.next()   // { value: 1, done: false }
g.return() // { value: undefined, done: true }

如果 Generator 函数内部有try...finally代码块,且正在执行try代码块,那么return方法会推迟到finally代码块执行完再执行。

function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }

# next()、throw()、return() 的共同点

next()、throw()、return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。

// next()是将yield表达式替换成一个值。
const g = function* (x, y) {
  let result = yield x + y;
  return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;

// throw()是将yield表达式替换成一个throw语句。
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));

// return()是将yield表达式替换成一个return语句。
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;

# yield* 表达式

ES6 提供了yield* 表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数。 不用yield* 表达式,在一个Generator函数内部,调用另一个Generator函数,需要在一个函数里调用for...of手动完成遍历

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

用 yield* 改写上面的例子

function* foo() {
  yield 'a';
  yield 'b';
}
function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}
// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

yield* 的另一个示例

function* inner() {
  yield 'hello!';
}
function* outer1() {
  yield 'open';
  yield inner();
  yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"


function* outer2() {
  yield 'open'
  yield* inner()
  yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"

yield*后面的 Generator 函数(没有return语句时),等同于在 Generator 函数内部,部署一个for...of循环。

function* concat(iter1, iter2) {
  yield* iter1;
  yield* iter2;
}
// 等同于
function* concat(iter1, iter2) {
  for (var value of iter1) {
    yield value;
  }
  for (var value of iter2) {
    yield value;
  }
}

// 例子2
function* gen(){
  yield* ["a", "b", "c"];
}
gen().next() // { value:"a", done:false }

// 例子3
let read = (function* () {
  yield 'hello';
  yield* 'hello';
})();
read.next().value // "hello"
read.next().value // "h"


// 例子4,yield* 返回值
function* foo() {
  yield 2;
  yield 3;
  return "foo";
}
function* bar() {
  yield 1;
  var v = yield* foo();
  console.log("v: " + v);
  yield 4;
}
var it = bar();

it.next()
// {value: 1, done: false}
it.next()
// {value: 2, done: false}
it.next()
// {value: 3, done: false}
it.next();
// "v: foo"
// {value: 4, done: false}
it.next()
// {value: undefined, done: true}


// 例子5
function* genFuncWithReturn() {
  yield 'a';
  yield 'b';
  return 'The result';
}
function* logReturned(genObj) {
  let result = yield* genObj;
  console.log(result);
}
[...logReturned(genFuncWithReturn())]
// The result
// 值为 [ 'a', 'b' ]


// 例子6
function* iterTree(tree) {
  if (Array.isArray(tree)) {
    for(let i=0; i < tree.length; i++) {
      yield* iterTree(tree[i]);
    }
  } else {
    yield tree;
  }
}
const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
for(let x of iterTree(tree)) {
  console.log(x);
}
// a
// b
// c
// d
// e


// 例子7,遍历二叉树
// 下面是二叉树的构造函数,
// 三个参数分别是左树、当前节点和右树
function Tree(left, label, right) {
  this.left = left;
  this.label = label;
  this.right = right;
}
// 下面是中序(inorder)遍历函数。
// 由于返回的是一个遍历器,所以要用generator函数。
// 函数体内采用递归算法,所以左树和右树要用yield*遍历
function* inorder(t) {
  if (t) {
    yield* inorder(t.left);
    yield t.label;
    yield* inorder(t.right);
  }
}
// 下面生成二叉树
function make(array) {
  // 判断是否为叶节点
  if (array.length == 1) return new Tree(null, array[0], null);
  return new Tree(make(array[0]), array[1], make(array[2]));}let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
// 遍历二叉树
var result = [];for (let node of inorder(tree)) {
  result.push(node);
}
result
// ['a', 'b', 'c', 'd', 'e', 'f', 'g']

# Generator函数作为对象属性

let obj = {
    * myGeneratorMethod() {
        // ... 
    }
}

// 另一种写法
let obj = {
    myGeneratorMethod: function* () {
        // ....
    }
}

# Generator 函数的this

Generator 函数总是返回一个遍历器,ES6 规定 这个遍历器是Generator函数的实例,也继承了Generaotr函数的prototype对象上的方法

function* g() {}
g.prototype.hello = function () {
  return 'hi!';
};
let obj = g();
obj instanceof g // true
obj.hello() // 'hi!'


// 如果把g当做普通的构造函数,并不会生效,g返回的总是遍历器对象,而不是this对象
function* g() {
  this.a = 11;
}
let obj = g();
obj.next();
obj.a // undefined

// Generator函数也不能跟new命令一起使用,会报错
function* F() {
  yield this.x = 2;
  yield this.y = 3;
}
new F()
// TypeError: F is not a constructor

用call绑定this

function* F() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}
var obj = {};
var f = F.call(obj);

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}
obj.a // 1
obj.b // 2
obj.c // 3


// 挂载在原型属性上
function* F() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}
var f = F.call(F.prototype);

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3

# 应用

# 控制流管理

step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
      step4(value3, function(value4) {
        // Do something with value4
      });
    });
  });
});

// Promise优化
Promise.resolve(step1)
  .then(step2)
  .then(step3)
  .then(step4)
  .then(function (value4) {
    // Do something with value4
  }, function (error) {
    // Handle any error from step1 through step4
  })
  .done();

// Generator优化
function* longRunningTask(value1) {
  try {
    var value2 = yield step1(value1);
    var value3 = yield step2(value2);
    var value4 = yield step3(value3);
    var value5 = yield step4(value4);
    // Do something with value4
  } catch (e) {
    // Handle any error from step1 through step4
  }
}

# Generator函数的异步应用

var fetch = require('node-fetch');

function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}

var g = gen();
var result = g.next();
result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
});

# 部署 Iterator 接口

function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
  }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
  console.log(key, value);
}

// foo 3
// bar 7
上次更新: 2020/10/28 18:32:03