重学JS | 异步编程 Generator()

242 阅读2分钟

这是我参与更文挑战的第9天,活动详情查看:更文挑战

[重学JavaScript系列文章连载中...]

Generator()函数是ES6的另一种异步编程解决方案。从语法上可以把它理解为一种状态机,内部维护了多个状态,函数执行后会返回一个拥有Iterator接口的对象,通过这个对象可以依次获取Generator()函数内部的每一个状态。

简单用法如下:

function* generator(){
  yield 'one'
  yield 'two'
}

let g = generator() // 获取拥有Iterator接口的对象
g.next() // 输出: {value: "one", done: false}
g.next() // 输出: {value: "two", done: false}
g.next() // 输出: {value: undefined, done: true}

从例子可以看出,Generator()函数的特性:

  1. function关键字和函数名之间有一个星号("*")
  2. 内部使用yield关键字来定义内部不同的状态
  3. Generator()函数不是构造函数,默认情况下不能使用new关键字,否则会报错,且内部的this也是无效的。
  4. Generator()函数执行后,函数体并没有直接执行,而是返回一个拥有Iterator接口的对象
  5. 调用next()函数才执行Generator()函数的函数体,遇到yield关键字暂停,当然若遇到return语句,则整个函数执行结束,后面的yield语句也都失效。
  6. next()函数返回的是一个含有value和done属性的对象,value表示的是yield表达式执行的结果
  7. 所有yield语句执行完毕时,函数会执行到末尾,如果有return语句,会将return语句的表达式作为value返回,否则返回undefined,同时done为true,表示遍历结束。

再来看个例子:

function* generator(){
  yield 'one'
  yield 'two'
  return 'three'
}
let p = generator()
for(let key of p){
  console.log(key)  // 先后输出 one two
}

Generator()函数返回的是一个拥有Iterator接口的对象,因此可以用for...of进行遍历,遍历的结果是yield表达式的返回值。

如何让对象类型的值遍历支持for...of呢?通过Generator()函数,看例子:

function* propGenerator(){
   let props = Object.keys(this)
   for(let p of props){
      // 通过yield控制每轮返回的值为属性名和属性值构成的数组
      yield [p,this[p]]
   }
}
let obj = {
  a:1,
  b:2
}
obj[Symbol.iterator] = propGenerator
for(let [p,v] of obj){
  console.log(p+':'+v)  // 依次输出: a:1  b:2
}

最后,看看Generator()函数嵌套的问题,可以采用yield* 表达式以支持嵌套使用,看例子:

function* f1(){
  yield '1'
}
function* f2(){
  yield '2',
  yield* f1()
  yield '3'
}
let f = f2()
for(let k of f){
  console.log(k)  // 依次输出:2 1 3
}

至此,我们学习了ES6中异步编程的另一个解决方案:Generator()函数。