# 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