ChuChencheng / note

菜鸡零碎知识笔记
Creative Commons Zero v1.0 Universal
3 stars 0 forks source link

ES6 Generator #29

Open ChuChencheng opened 4 years ago

ChuChencheng commented 4 years ago

概念

一种异步编程的解决方案,与传统函数的语法、行为完全不同

语法

function 关键字与函数名之间加个 * 号。

function* gen () {}

内部使用 yield 关键字,执行后返回一个迭代器(Iterator),没错,就是用 next 方法去遍历的迭代器:

function* gen () {
  yield 1
  yield 1 + 1
  return 3
}

// Iterator
const it = gen()
it.next() // { value: 1, done: false }
it.next() // { value: 2, done: false }
it.next() // { value: 3, done: true }

可以使函数“暂停”在某个阶段,等到执行了 next 方法后再继续执行。

Generator 函数返回的迭代器在没有执行 next 是不会执行的:

function* gen () {
  console.log('executed')
}

const it = gen()
it.next()
// executed

另外注意, 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
}

for...of 循环

既然是 Iterator ,就可以用 for...of 循环来遍历它:

function* gen () {
  yield 1
  yield 2
  yield 3
  return 4
}

for (let value of gen()) {
  console.log(value)
}
//  1 2 3
// 注意, `for...of` 循环不会输出结果为 `done: true` 的值

next 函数的参数

在执行 next 时,如果给它传一个参数,这个参数会作为上一个 yield 表达式的返回值。

如果不传,其实就相当于传了 undefined

function* gen () {
  const i = yield 1
  console.log(i)
  yield i + 1
}

const it = gen()
const y1 = it.next() // { value: 1, done: false }
it.next(y1.value + 1)
// 函数内部输出 2
// next 返回值: { value: 3, done: false }
it.next() // { value: undefined, done: true }

Generator.prototype.throw()

可以在 Generator 函数内部抛出一个错误,如果 Generator 内部没有处理,则会抛出到外部:

function* gen () {
  try {
    yield
  } catch (e) {
    console.log('Generator 函数内部捕获', e)
  }
}

const it = gen()
it.next() // 先执行到第一个 yield 处
it.throw('手动抛出一个错误')
// Generator 函数内部捕获 手动抛出一个错误

Generator.prototype.return()

相当于提前把这个迭代器结束遍历,即返回的 done 变为 true

function* gen () {
  yield 1
  yield 2
  yield 3
  return 4
}

const it = gen()
it.next() // { value: 1, done: false }
it.return() // { value: undefined, done: true }
it.next() // { value: undefined, done: true }

return 的第一个参数可以指定 value 的值:

function* gen () {
  yield 1
  yield 2
  yield 3
  return 4
}

const it = gen()
it.next() // { value: 1, done: false }
it.return('foo') // { value: 'foo', done: true }
it.next() // { value: undefined, done: true }

next, throw, return

三个函数其实可以理解为,把执行函数时起点的 yield 替换成了不同的语句:

function* gen () {
  const r = yield 1
}

const it = gen()
it.next() // 执行到第一个 yield

it.next()
// 相当于把 `const r = yield 1` 替换成 `const r = undefined`

// it.throw(new Error('error'))
// 相当于把 `const r = yield 1` 替换成 `const r = throw new Error('error')`

// it.return(6)
// 相当于把 `const r = yield 1` 替换成 `const r = return 6`

yield* 表达式

如果在 Generator 函数内部调用 Generator 函数,可以用 yield* 表达式,这样外层在遍历 Generator 时,遇到内部的 Generator 函数,会转而进入内部函数遍历,不会跳过:

function* bar () {
  yield 1
  yield 2
}

function* foo () {
  yield 3
  // 这边如果没有手动去遍历 `bar()` 返回的迭代器, `foo` 的下一个 `next` 函数就会执行到 `yield 4` 了
  bar()
  yield 4
}

const it = foo()
for (let value of it) {
  console.log(value)
}
// 3 4

手动遍历:

function* bar () {
  yield 1
  yield 2
}

function* foo () {
  yield 3
  for (let value of bar()) {
    yield value
  }
  yield 4
}

const it = foo()
for (let value of it) {
  console.log(value)
}
// 3 1 2 4

使用 yield* 表达式:

function* bar () {
  yield 1
  yield 2
}

function* foo () {
  yield 3
  yield* bar()
  yield 4
}

const it = foo()
for (let value of it) {
  console.log(value)
}
// 3 1 2 4

作为对象属性

Generator 函数作为对象属性的写法:

const obj = {
  * generatorProperty () {
    yield 1
  }
}

this

由于 Generator 函数返回的是一个 Iterator ,而不是 this 对象,因此在 Generator 函数内绑定在 this 上的属性都是无效的:

function* gen () {
  this.a = 1
}

const it = gen()
console.log(it instanceof gen) // true , 这是 ES6 规定的,返回的迭代器是 Generator 函数的实例
console.log(it.a) // undefined

此外, Generator 函数也不能作为构造函数而使用 new 命令:

function* gen () {
  this.a = 1
}

const it = new gen() // TypeError: gen is not a constructor

Generator 与协程、上下文,应用

这部分见峰哥的文章吧。

参考

http://es6.ruanyifeng.com/#docs/generator