// 使用 function* 定义一个 generator 函数 function* helloWorldGenerator() { yield 'hello'; // yield 关键字作为暂停的点 yield 'world'; return 'ending'; } var hw = helloWorldGenerator(); // 执行 generator 函数,返回一个遍历器对象。而这时,这个函数内的代码并不会执行。
// 调用遍历器对象的 next 方法,执行函数内的代码,执行到下一个 yield 的位置,并暂停执行 hw.next() // { value: 'hello', done: false } value 是 yield 后面跟的表达式的值,done 是 genertator 函数结束状态 // 再次调用 next,执行到下一个 yield 位置 hw.next() // { value: 'world', done: false } // 执行结束,value 值为 return 的值,没有 return 则为 undefined(函数没 return 返回 undefined),done 变为 true hw.next() // { value: 'ending', done: true } // 还可以无限次调用 next,但是都返回相同的对象 hw.next() // { value: undefined, done: true }
yield 不能用在普通函数中:
var flat = function* (a) { // forEach 方法是个普通函数,在里面使用了 yield 会报错。解决方法是改为 for 循环 a.forEach(function (item) { if (typeof item !== 'number') { yield* flat(item); } else { yield item; } } };
yield
语句如果用在一个表达式之中,必须放在圆括号里面。
console.log('Hello' + yield); // SyntaxError console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield)); // OK console.log('Hello' + (yield 123)); // OK
next方法的参数
function* foo(x) { var y = 2 * (yield (x + 1)); // yield 语句在表达式中,需要将 yield 语句括起来,否则报错 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 } 调用第一次 next 开始执行,得到第一个 yield 的返回值 6。由于 next 参数为上一个 yield 语句的值,所以第一个 next 传入参数没有意义 b.next(12) // { value:8, done:false } 调用 next 方法时注入了数据,作为上一个 yield 语句的值,得到 var y = 2 * 12 b.next(13) // { value:42, done:true } 得到 var z = 13
for...of循环
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
循环之中。
原生的JavaScript对象没有遍历接口,无法使用for...of
循环,通过Generator函数为它加上这个接口,就可以用了。
// 第一种方法 function* objectEntries(obj) { let propKeys = Reflect.ownKeys(obj); for (let propKey of propKeys) { yield [propKey, obj[propKey]]; } } let jane = { first: 'Jane', last: 'Doe' }; for (let [key, value] of objectEntries(jane)) { console.log(`${key}: ${value}`); } // 第二种方法 function* objectEntries() { let propKeys = Object.keys(this); for (let propKey of propKeys) { yield [propKey, this[propKey]]; } } let jane = { first: 'Jane', last: 'Doe' }; jane[Symbol.iterator] = objectEntries; for (let [key, value] of jane) { console.log(`${key}: ${value}`); }
Generator.prototype.throw()
Generator函数返回的遍历器对象,都有一个throw
方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。
var g = function* () {
// 使用 try...catch... 进行异常捕获
try { yield; } catch (e) { console.log('内部捕获', e); } }; var i = g(); i.next(); try { i.throw('a'); // 这里使用 throw 方法抛出的错误,会由 generator 函数内的 catch 处理 i.throw('b'); // generator 内的 catch 已经执行过了,就不会再被 generator 的 catch 捕获了,由外部的 catch 捕获 } catch (e) { console.log('外部捕获', e); } // 内部捕获 a // 外部捕获 b
如果Generator函数内部没有部署try...catch
代码块,那么throw
方法抛出的错误,将被外部try...catch
代码块捕获。
如果Generator函数内部和外部,都没有部署try...catch
代码块,那么程序将报错,直接中断执行。
throw
方法被捕获以后,会附带执行下一条yield
语句。也就是说,会附带执行一次next
方法。
var gen = function* gen(){ try { yield console.log('a'); } catch (e) { // ... } yield console.log('b'); // throw 方法会附带执行 next,从而执行到这个 yield 位置 yield console.log('c'); } var g = gen(); g.next() // a g.throw() // b g.next() // c
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 } value 值变成了 return 的参数 g.next() // { value: undefined, done: true } return 方法 导致 generator 函数结束,所以 value 为 undefined
yield*语句
function* foo() { yield 'a'; yield 'b'; } function* bar() { yield 'x'; // foo(); 如果只是单纯的执行 foo() 函数,只是得到一个遍历器对象,并不会产生什么效果。 yield* foo(); // 使用了 yield* 语句,在遍历的时候才会遍历这个 generator 函数内部的 generator 函数。 yield 'y'; } for (let v of bar()){ console.log(v); } // "x" // "a" // "b" // "y"
function* gen(){ yield* ["a", "b", "c"]; // 数组、字符串等,带有 iterator 接口的,都可以被 yield* 遍历 } gen().next() // { value:"a", done:false }
Generator与状态机
var clock = function*() { while (true) { console.log('Tick!'); // 执行状态1代码 yield; console.log('Tock!'); // 执行状态2代码 yield; } };
每次调用 next() 就可以在两种状态间切换执行,而不需要使用一个布尔变量来做判断