基本概念

Generator 函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。

语法上:

Generator 函数是一个状态机,封装了多个内部状态。

形式上:

Generator 是个普通函数,但有两个特征: 一是: function 关键字与函数名之间有一个星号;二是: 函数体内部使用yield 表达式,定义不同的内部状态。

  1. function* helloWorldGenerator() {
  2. yield 'hello'
  3. yield 'world'
  4. return 'ending'
  5. }
  6. var hw = helloWorldGenenrator()

该函数有三个状态: hello, world 和 return 语句。

  1. // 调用
  2. hw.next() // {value: 'hello', done: false}
  3. hw.next() // {value: 'world', done: false}
  4. hw.next() // {value: 'ending', done: false}
  5. hw.next() // {value, undefined, done: true}

调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着 value 和 done 两个属性的对象。value 属性表示当前内部状态的值,是yield 表达式后面那个表达式的值; done 属性是一个布尔值,表示是否结束遍历。

yield表达式

yield 表达式是暂停标志。

遍历器对象的next方法的运行逻辑:

  1. 遇到yield 表达式,就暂停执行后面的操作,并将yield 后的表达式的值,作为返回对象的value属性值。
  2. 下一次调用next方法时,再继续往下执行,直到遇到下一个yield 表达式。
  3. 如果没有遇到新的yield表达式,就一直运行,直到遇到return 语句,并将return 后的表达式的值,作为返回对象的vlaue属性值。
  4. 如果该函数没有return 语句,则返回的对象的value属性值为undefined.

yield 与 return
相似:
都能返回紧跟在语句后的表达式的值。
区别:
每次遇到yield,函数就暂停执行,下一次再从该位置继续往后执行。
一个函数执行多次yield 表达式,而只能执行一次return 语句。

yield表达式只能用在 Generator 函数里面,用在其它地方都会报错

  1. var arr = [1, [[2, 3], 4], [5, 6]]
  2. var flat = function* (a) {
  3. a.forEach(function(item) {
  4. if (typeof item !== 'number') {
  5. yield* flat(item)
  6. } else {
  7. yield item
  8. }
  9. })
  10. }
  11. for (var f of flat(arr)) {
  12. console.log(f)
  13. }

上面的代码会产生语法错误,因为 yield 表达式不能直接用在 forEach函数里。

  1. var arr = [1, [[2, 3], 4], [5, 6]]
  2. var flat = function* (a) {
  3. var len = a.length;
  4. for (let i = 0; i < len; i++) {
  5. var item = a[i]
  6. if (typeof item !== 'number') {
  7. yield* flat(item)
  8. } else {
  9. yield item
  10. }
  11. }
  12. }
  13. for (var f of flat(arr)) {
  14. console.log(f)
  15. }
  16. // log
  17. 1
  18. 2
  19. 3
  20. 4
  21. 5
  22. 6

yield 表达式如果用在另一个表达式之中,必须放在圆括号里面

  1. function* demo() {
  2. console.log('Hello' + yield); // SyntaxError
  3. console.log('Hello' + yield 123); // SyntaxError
  4. console.log('Hello' + (yield)) // ok
  5. console.log('Hello' + (yield 123)) // ok
  6. }

yield 表达式用作函数参数或放在赋值表达式的右边,可以不加括号。

  1. function* demo() {
  2. foo(yield 'a', yield 'b'); // ok
  3. let input = yield; // ok
  4. }

与 Iterator 接口的关系

Generator 函数就是遍历器生成函数,可以把 Generator赋值给对象的Symbol.iterator 属性,从而使得该对象具有Iterator接口。

  1. var myIterable = {}
  2. myIterable[Symbol.iterator] = function* () {
  3. yield 1;
  4. yield 2;
  5. yield 3;
  6. }
  7. [...myIterable] // [1, 2, 3]

Generator 函数执行后,返回一个遍历器对象,该对象本身具有 Symbol.iterator 属性,执行后返回自身。

next 方法的参数

yield 表达式本身没有返回值,或者说总是返回undefined。next 方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

  1. function* f() {
  2. for (let i = 0; true; i++) {
  3. var reset = yield i;
  4. if (reset) { i = -1 }
  5. }
  6. }
  7. var g = f()
  8. g.next() // {value: 0, done: false }
  9. g.next() / {value: 1, done: false }
  10. g.next(true) // {value: 0, done: false }

上例定义了一个可以无限运行的Generator 函数f,如果next 方法没有参数,每次运行到yield表达式,变量reset的值总是undefined。当next方法带有一个参数是,变量reset 就被重置为这个参数。

Generator 函数从暂停状态到恢复执行,它的上下文状态是不变的。通过next 方法的参数,可以在Generator 函数开始运行后,继续向函数体内注入值。即可在Generator 函数运行的不同阶段,从外部向内部注入不同的值。

  1. function* foo(x) {
  2. var y = 2 * (yield(x + 1))
  3. var z = yield(y / 3)
  4. return (x + y + z)
  5. }
  6. var a = foo(5)
  7. a.next() // {value: 6, done: false}
  8. a.next() // {value: NaN, done: false}
  9. a.next() // {value: NaN, done: false}
  10. var b = foo(5);
  11. b.next() // {value: 6, done: false}
  12. b.next(12) // {value: 8, done: false}
  13. b.next(13) // {vlaue: 42, done: true}

注意: 由于next 方法的参数表示上一个yield表达式的返回值,所以第一次使用next方法时,传递餐宿时无效的。V8引擎直接忽略第一次使用next方法的参数,只有从第二次使用next放开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

  1. function* dataConsumer() {
  2. console.log('Started')
  3. console.log(`1, ${yield}`)
  4. conosle.log(`2, ${yield}`)
  5. return 'result'
  6. }
  7. let genObj = dataConsumer()
  8. genObj.next()
  9. // Started
  10. genObj.next('a')
  11. // 1, a
  12. genObj.next('b')
  13. // 2, b

如果要第一次调用next方法就能够输入值,可以在Generator函数外面再包一层。

  1. function wrapper(generatorFunction) {
  2. return function(...args) {
  3. let generatorObject = generatorFunction(...args)
  4. generatorObject.next()
  5. return generatorObject;
  6. }
  7. }
  8. const wrapped = wrapper(function* () {
  9. conosle.log(`First input: ${yield}`)
  10. return 'DONE'
  11. })
  12. wrapped().next('hello!')
  13. // First input: hello!

for … of 循环

for…of 循环可以自动遍历Generator 函数运行时生成的Iterator 对象,且此时不需要调用next方法。

  1. function* foo() {
  2. yield 1;
  3. yield 2;
  4. yield 3;
  5. yield 4;
  6. yield 5;
  7. return 6;
  8. }
  9. for (let v of foo()) {
  10. console.log(v)
  11. }
  12. // 1 2 3 4 5

注意: 一旦next方法的返回对象done属性为true, for…of 循环就会中止,且不包含返回对象,所以return 语句返回的6,并不包含在 for…of 循环中。

  1. function* fibonacci() {
  2. let [prev, curr] = [0, 1]
  3. for (;;) {
  4. yield curr;
  5. [prev, curr] = [curr, prev + curr]
  6. }
  7. }
  8. for (let n of fibonacci()) {
  9. if (n > 1000) break
  10. console.log(n)
  11. }
  1. function* objectEntries(obj) {
  2. let porpKeys = Reflect.ownKeys(obj);
  3. for (let propKey of propKeys) {
  4. yield [propKey, obj[propKey]]
  5. }
  6. }
  7. let jane = {first: 'Jane', last: 'Doe'}
  8. for (let [key, vlaue] of objectEntries(jane)) {
  9. console.log(`${key}: ${value}`)
  10. }
  11. // first: Jane
  12. // last: Doe

将Generator 函数加到对象的Symbol.Iterator 属性上面:

  1. function* objectEntries() {
  2. let propKeys = Object.keys(this)
  3. for (let propKey of propkes) {
  4. yield [propKey, this[propKey]]
  5. }
  6. }
  7. let jane = {first: 'Jane', last: 'Doe'}
  8. jane[Symbol.iterator] = objectEntries;
  9. for(let [key, value] of jane) {
  10. console.log(`${key}: ${value}`)
  11. }
  12. // first: Jane
  13. // last: Doe
  1. function* numbers() {
  2. yield 1
  3. yield 2
  4. return 3
  5. yield 4
  6. }
  7. // 扩展运算符
  8. [...numbers()] // [1, 2]
  9. // Array.from 方法
  10. Array.from(numbers()) // [1, 2]
  11. // 解构赋值
  12. let [x, y] = numbers()
  13. x // 1
  14. y // 2
  15. // for ... of 循环
  16. for (let n of numbers()) {
  17. console.log(n)
  18. }
  19. // 1
  20. // 2

Generator.prototype.throw()

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

  1. var g = function* () {
  2. try {
  3. yield;
  4. } catch(e) {
  5. console.log('内部捕获', e)
  6. }
  7. }
  8. var i = g()
  9. i.next()
  10. try {
  11. i.throw('a')
  12. i.throw('b')
  13. } catch (e) {
  14. console.log('外部捕获', e)
  15. }
  16. // 内部捕获 a
  17. // 外部捕获 b

遍历器对象i 连续抛出两个错误,第一个错误被Generator 函数体内的catch捕获。i 第二次抛出错误,由于Generator 函数内部的 catch 语句已经执行过了,不会再捕捉这个错误,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch 语句捕获。

throw 方法可接受一个参数,该参数会被catch 语句接受,建议抛出Error 对象的实例。

  1. var g = function* () {
  2. try {
  3. yield
  4. } catch (e) {
  5. console.log(e)
  6. }
  7. }
  8. var i = g()
  9. i.next()
  10. i.throw(new Error('出错了'))
  11. // Error: 出错了

Generator.prototype.return()

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

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

如果 return 方法调用时,不提供参数,则返回值的value属性为 undefined

  1. function* gen() {
  2. yield 1;
  3. yield 2;
  4. yield 3;
  5. }
  6. v g = gen()
  7. g.next() // {value: 1, done: false}
  8. g.return() // {value: undefined, done: true}

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

  1. function* numbers() {
  2. yield 1;
  3. try {
  4. yield 2;
  5. yield 3;
  6. } finally {
  7. yield 4;
  8. yield 5;
  9. }
  10. yield 6;
  11. }
  12. var g = numbers()
  13. g.next() // {value: 1, done: false}
  14. g.next() // {value: 2, done: false}
  15. g.return(7) // {value: 4, done: false}
  16. g.next() // {value: 5, done: false}
  17. g.next() // {value: 7, done: true}

上例,调用return 方法后,就开始执行finally 代码块,然后就等到finally代码块执行完,再执行return 方法。

next(), throw(), return() 的共同点

它们的作用都是让 Geneartor 函数恢复执行,并且使用不同的语句替换yield 表达式。

next() 是将yield 表达式替换一个值

  1. const g = function* (x, y) {
  2. let result = yield x + y;
  3. return result;
  4. }
  5. console gen = g(1, 2)
  6. gen.next() // {value: 3, done: false}
  7. gen.next(1) // {value: 1, done: true}
  8. // let result = yield x + y => let result = 1

第二个 next(1) 方法就相当于将yield 表达式替换成一个值 1; 如果next 方法没有参数,就相当于替换成undefined。

throw()是将yield 表达式替换成一个 throw语句

  1. const g = function* (x, y) {
  2. let result = yield x + y;
  3. return result;
  4. };
  5. const gen = g(1, 2)
  6. gen.throw(new Error('出错了'))
  7. // let result = yield x + y; => let result = throw(new Error('出错了'))

return() 是将yield 表达式替换成一个return 语句

  1. gen.return(2); // {value: 2, done: true}
  2. // let result = yield x + y; => let result = return 2;

yield* 表达式

默认情况下,不能直接在 Generator 函数内部调用另一个Generator函数。

但可以用 yield* 表达式,可在Generator 函数内部执行另一个Generator函数

  1. function* foo() {
  2. yield 'a';
  3. yield 'b';
  4. }
  5. function* bar() {
  6. yield 'x';
  7. yield* foo();
  8. yield 'y';
  9. }
  10. // 等同于
  11. function* bar() {
  12. yield 'x';
  13. yield 'a';
  14. yield 'b';
  15. yield 'y';
  16. }
  17. // 等同于
  18. function* bar() {
  19. yield 'x';
  20. for(let v of foo()) {
  21. yield v;
  22. }
  23. yield 'y';
  24. }
  1. function* inner() {
  2. yield 'hello!'
  3. }
  4. function* outer() {
  5. yield 'open';
  6. yield inner();
  7. yield 'close'
  8. }
  9. var gen = outer1()
  10. gen.next().value // 'open'
  11. gen.next().value // 返回一个遍历器对象
  12. gen.next().value // 'close'
  13. function* outer2() {
  14. yeild 'open';
  15. yield* inner();
  16. yield 'close'
  17. }
  18. var gen = outer2()
  19. gen.next().value // 'open'
  20. gen.next().value // 'hello!'
  21. gen.next().value // 'close'

yield 后面的Generator 函数(没有return 语句时),不过是 for … of 的一种简写形式。
若是 return 语句时,则需要用 var value = yield
iterator 的形式获取 return 语句的值。

  1. function* concat(iter1, iter2) {
  2. yield* iter1;
  3. yield* iter2;
  4. }
  5. // 等同于
  6. function* concat(iter1, iter2) {
  7. for (var value of iter1) {
  8. yield value;
  9. }
  10. for (var value of iter2) {
  11. yield value
  12. }
  13. }

如果yield* 后面跟着一个数组,由于原生数组支持遍历器,因此会遍历数组成员.

  1. function *gen() {
  2. yield* ['a', 'b', 'c'];
  3. }
  4. gen().next() // {value: 'a', done: false}

yield 命令后面如果不加星号,返回的整个数组,加了星号就表达返回的是数组的遍历器对象。

实际上,只要有Iterator 接口的数据结构,都可以被 yield* 遍历。

  1. let read = (function* () {
  2. yield 'hello';
  3. yield* 'hello';
  4. })();
  5. read.next().value // 'hello'
  6. read.next().value // 'h'

如果被代理的 Generator 函数有 return 语句,那么可以向代理它的Generator 函数返回数据。

  1. function* foo() {
  2. yield 2;
  3. yield 3;
  4. return 'foo';
  5. }
  6. function* bar() {
  7. yield 1;
  8. var v = yield* foo();
  9. console.log('v: ' + v)
  10. yield 4;
  11. }
  12. var it = bar();
  13. it.next() // {value: 1, done: false}
  14. it.next() // {value: 2, done: false}
  15. it.next() // {value: 3, done: false}
  16. it.next()
  17. // 'v: foo'
  18. // {value: 4, done: false}
  19. it.next(); // {value: undefined, done: true}

作为对象属性的 Generator 函数

  1. let obj = {
  2. * myGeneratorMethod() {
  3. ...
  4. }
  5. }
  6. // 等同于
  7. let obj = {
  8. myGeneratorMethod: function* () {
  9. }
  10. }

myGeneratorMethod 属性前面有一个星号,表示这个属性是一个 Generator 函数。

Generator 函数的this

Generator函数的遍历器是 Generator 函数的实例,也继承了 Generator 函数的 prototype 对象上的方法。

  1. function* g() {}
  2. g.prototype.hello = function() {
  3. return 'hi'
  4. }
  5. let obj = g();
  6. obj instanceof g; // true
  7. obj.hello() // 'hi'

g 返回的总是遍历器对象,而不是this 对象。

  1. function* g() {
  2. this.a = 11
  3. }
  4. let obj = g()
  5. obj.next();
  6. obj.a // undefined

Generator 函数 g 在 this 对象上面添加了一个属性a, 但是obj 对象拿不到这个属性。

Generator 函数也不能直接跟 new 命令一起使用,否则会报错。

  1. function* F() {
  2. yield this.x = 2;
  3. yield this.y = 3
  4. }
  5. new F(); // TypeError: F is not a constructor

因为 F 不是构造函数,所以不能 new 命令一起使用。

使用 this 的变通方法:

首先,生成一个空对象,使用 call 方法绑定 Generator 函数内部的this。

  1. function* F() {
  2. this.a = 1;
  3. yield this.b = 2;
  4. yield this.c = 3;
  5. }
  6. var obj = {}
  7. var f = F.call(obj)
  8. f.next(); // {value: 2, done: false}
  9. f.next(); // {value: 3, done: false}
  10. f.next(); // {value: undefined, done: true}
  11. obj.a // 1
  12. obj.b // 2
  13. obj.c // 3
  1. function* gen() {
  2. this.a = 1
  3. yield this.b = 2
  4. yield this.c = 3
  5. }
  6. function F() {
  7. return gen.call(gen.prototype)
  8. }
  9. var f = new F()
  10. f.next(); // {value: 2, done: false}
  11. f.next(); // {value: 3, done: false}
  12. f.next(); // {value: undefined, done: true}
  13. f.a // 1
  14. f.b // 2
  15. f.c // 3

Generator 与状态机

Generator 是实现状态机的最佳结构。

  1. var ticking = true;
  2. var clock = function() {
  3. if (ticking) {
  4. console.log('Tick!')
  5. } else {
  6. console.log('Tock!')
  7. }
  8. ticking = !ticking
  9. }

clock 函数一共两个状态,运行一次就改变一次。这个函数用 Generator实现如下:

  1. var clock = function* () {
  2. while (true) {
  3. console.log('Tick!')
  4. yield;
  5. console.log('Tock!')
  6. yield;
  7. }
  8. }

Generator 实现 更简洁、更安全、更符合函数式编程的思维,写法上也更优雅。

应用

Generator 可以暂停函数执行,返回任意表达式的值。