生成器函数
在写其他 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 其具备下列方法:
next(inputValue?): { value, done }
- (从头,或者从上次停止的位置)开始运行生成器函数体,直到遇到
yield
或者return
- 注意:此函数可以有一个可选的参数
inputValue
。可以在继续运行生成器函数前,为上次停顿所在的 yield 表达式设置一个值。
- (从头,或者从上次停止的位置)开始运行生成器函数体,直到遇到
throw(e): { value, done }
- 如果生成器函数没开始运行,则等同于原地 throw
- 否则,在生成器函数体当前
yield
停顿的位置 throw 一个异常,然后继续运行,直到遇到yield
、return
或者未被捕获的异常(见下文)
return(value): {value, done}
- 强行终止(即使生成器函数还没运行完毕),并指定一个 value 作为返回值
[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/