生成器
Generator
函数是 ES6
提供的一种异步编程解决方案
调用Generator
函数后,该函数并不执行,返一个指向内部状态的指针对象,即遍历器对象。
Generator
函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法可以恢复执行。
yield
表达式
- 遇到
yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。 - 如果没有再遇到新的
yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。 - 如果该函数没有
return
语句,则返回的对象的value
属性值为undefined
Generator
函数可以不用yield
表达式,这时就变成了一个单纯的暂缓执行函数。只有调用next
方法时,函数才会执行
function* f() {
console.log('执行了!')
}
var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
参数
next
方法的参数是上一个yield
表达式的返回值,所以在第一次使用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 }
第一次调用
b
的next
方法时,返回x+1
的值6;第二次调用next
方法,将上一次yield
表达式的值设为12,因此y
等于24,返回y / 3
的值8;第三次调用next
方法,将上一次yield
表达式的值设为13,因此z
等于13,这时x
等于5,y
等于24,所以return
语句的值等于42
for...of
可以自动遍历 Generator
函数运行时生成的Iterator
对象,且此时不再需要调用next
方法
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
注意:一旦next
方法的返回对象的done
属性为true
,for...of
循环就会中止,且不包含该返回对象,所以上面代码的return
语句返回的6,不包括在for...of
循环之中
throw()
throw
方法,可以在函数体外抛出错误,然后在 Generator
函数体内捕获
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
上面代码中,遍历器对象i
连续抛出两个错误。第一个错误被 Generator
函数体内的catch
语句捕获。i
第二次抛出错误,由于 Generator
函数内部的catch
语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator
函数体,被函数体外的catch
语句捕获。
接受参数
var g = function* () {
while (true) {
try {
yield;
} catch (e) {
if (e != 'a') throw e;
console.log('内部捕获', e);
}
}
};
var i = g();
i.next();
try {
throw new Error('a');
throw new Error('b');
} catch (e) {
console.log('外部捕获', e);
}
// 外部捕获 [Error: a]
之所以只捕获了a
,是因为函数体外的catch
语句块,捕获了抛出的a
错误以后,就不会再继续try
代码块里面剩余的语句了
特点
throw
方法被捕获以后,会附带执行一次next
方法.只要 Generator
函数内部部署了try...catch
代码块,那么遍历器的throw
方法抛出的错误,不影响下一次遍历。
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
next()
throw()
return()
的共同点
它们的作用都是让 Generator
函数恢复执行,并且使用不同的语句替换 yield
表达式
next()
是替换成一个值(如果没有参数,则是undefined
throw()
是替换成一个throw
语句return()
是替换成一个return
语句
yield*
用来在一个 Generator
函数里面执行另一个 Generator
函数
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
扩展
只要数据结构中有 Iterator
接口,就可以被yield*
遍历。如数组:
function* gen(){
yield* ["a", "b", "c"];
}
gen().next()
// { value:"a", done:false }
- 取出嵌套数组的所有成员
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
应用
异步操作的同步表达
function one(){
setTimeout(()=>{
console.log(111);
iterator.next();
},1000)
}
function two(){
setTimeout(()=>{
console.log(222);
iterator.next();
},2000)
}
function * gen(){
yield one();
yield two();
}
//调用生成器函数
let iterator = gen();
iterator.next();
部署 Iterator
接口
利用 Generator
函数,可以在任意对象上部署 Iterator
接口
const obj = { a: 1, b: 2, c: 3 }
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(obj)) {
console.log(key, '->', value);
}
// a -> 1
// b -> 2
// c -> 3