一个很简短的 JS 生成器入门和用法参考

2,852 阅读4分钟

生成器函数

在写其他 js 代码时会经常用到 debugger 的东西,能够让当前运行的函数暂停住。生成器函数里的 yield 关键词也能使得函数暂停运行,同时还能用来做数据的输入输出(如果是 debugger,只能手动去逐个 inspect 变量)。

yield 可以当作“断点”。yield value 除了能当作断点,还可以向外部输出 value

此外,yield 表达式本身也是可以有值的,也就是说还可以表示外部传入的数据。只要外部在调用 next(inputValue) 时输入一个值。 这就是为什么在赋值语句里,可以用 yield 作为右值(例如,在使用了 react-saga 的项目里到处都是这种玩意儿)。

function* fn1(){
  var data = yield "字符串反转器已启动"
  yield data.split("").reverse().join("")
}

// 创建 Generator 对象
var g = fn1()

// 运行函数,直到遇到第一个“断点”(yield 处)
console.log(g.next())

// 上次停顿的位置是 var data = yield "字符串反转器已启动"
// 因此可以在继续运行前,使得 (yield "字符串反转器已启动") 的值为 "Hello"
console.log(g.next("Hello")) 

// 输出:
// { value: '字符串反转器已启动', done: false }
// { value: 'olleH', done: false }

Generator

正如前面的示例,调用一个 function* 生成器函数,会返回一个 Generator 对象(上文的 g)。这个对象是可迭代对象(最常见的用法,即用于 for of 循环中)。

此外,可以把它当作一个控制器,控制着一个被 yield 打了断点(而且还没开始运行)的函数。

Generator 其具备下列方法:

  1. next(inputValue?): { value, done }
    • (从头,或者从上次停止的位置)开始运行生成器函数体,直到遇到 yield 或者 return
    • 注意:此函数可以有一个可选的参数 inputValue 。可以在继续运行生成器函数前,为上次停顿所在的 yield 表达式设置一个值。
  2. throw(e): { value, done }
    • 如果生成器函数没开始运行,则等同于原地 throw
    • 否则,在生成器函数体当前 yield 停顿的位置 throw 一个异常,然后继续运行,直到遇到 yieldreturn 或者未被捕获的异常(见下文)
  3. return(value): {value, done}
    • 强行终止(即使生成器函数还没运行完毕),并指定一个 value 作为返回值
  4. [Symbol.iterator]()
    • 用于迭代协议的。效果同直接调用 next()

yield*

yield 关键词相比,多了一个星号。可以把 yield* another_iterable 当作以下代码语法糖:

for (let item of another_iterable) {
  yield item;
}

也就是说,在生成器函数里yield* 就是针对一个可迭代对象,把它的每一项逐个地 yield 出来。

有趣的例子:

  • yield* [1,2,3] 会把3个数字逐个 yield 出来。数组是可迭代的。
  • yield* "abcdefg" 会把这7个字母逐个 yield 出来。字符串也是可迭代对象。
  • yield* another_generator 相当于把另一个 Generator 的输出,当作自己的输出给一个个 yield 出去了。
    • 这个有趣的特性可以用来做拦截器之类的应用。
    • 说到了串联,如果是当前生成器函数想要利用另外一个生成器的返回值,直接调用 another_generator.next().value 就行了

生成器函数内的异常

next() 或者 throw(e) 可以让生成器函数开始运行。在运行过程中,如果生成器函数里遇到了未被捕获的异常(可以是生成器内部自己产生的,或者由外部调用 throw(e) 塞进去的),那么会在外部由 next() 或者 throw(e) 给 throw 出来

function* test(){
  try {
    console.log("inner: Hello")
    console.log("inner: GET" + (yield "output1"))
  } catch (err) {
    console.log("inner: Caught", err)
  }
  console.log("inner: Done")
  return "output 2"
}

var g = test()
console.log("outer: next: ", g.next("input 1"))
console.log("outer: throw: ", g.throw("err"))
console.log("outer: next: ", g.next("input 2"))

上面的例子输出如下。注意到由外部提供的 input1 不会被输出,因为那是生成器函数还没开始运行的时候传进去的,能传到哪里?没有任何的意义。

inner: Hello
outer: next:  { value: 'output1', done: false }
inner: Caught err
inner: Done
outer: throw:  { value: 'output 2', done: true }
outer: next:  { value: undefined, done: true }

安利

写了一个 Markdown 编辑器组件,只要一个框,所见即所得,而且还全面兼容 CodeMirror(一个很强大的代码编辑组件)

悄悄安利一下: laobubu.net/HyperMD/

(欢迎来 GitHub 点赞 或者帮我买杯咖啡