Skip to content

JavaScript之generator函数的自动执行 #11

@chenyong9528

Description

@chenyong9528

关于generator函数

generator在《ECMAScript 6 入门》中的定义:

Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象(Iterator),也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

简单看看它的使用:

function* gen(n) {
  const x = yield n + 5
  const y = yield x + 5
  return y
}

const it = gen(5)

it.next() // { value: 10, done: false }
it.next(10) // { value: 15, done: false }
it.next(20) // { value: 20, done: true }
it.next() // { value: undefined, done: true }

值得注意的几个地方:

  1. 调用generator函数并不会执行其中的代码,而是返回一个遍历器对象
  2. 调用遍历器对象next方法generator函数才开始执行,并停在第一个yield的地方,再次调用next,函数恢复执行,停在下一个yield,如此直到函数执行完毕
  3. next方法的返回值是一个对象,该对象value属性是yield后面的内容
  4. next方法的参数,是上下一个yield的返回值,这使得我们可以不停向generator函数中传递数据
  5. 可以有多个yield,但只能有一个return

当然,在我们平时使用中,yield后面一般会是Promise

generator函数对我们来说其实并不陌生,async作为generator的语法糖,使用async其实间接的使用了generator,实现generator的自动执行,就是为了帮我们更好的了解async函数的工作方式。在async之前,就已经出现了类似的解决方案,如co模块,它能够实现generator的自动执行,后来将它纳入了标准中,所以,我们要实现的,其实就是co模块。

关于generator的更多细节可以参考阮一峰老师的《ECMAScript 6 入门》

实现

我们的目的在于了解generator函数自动执行的核心逻辑,所以,要实现的是理想状态下的co模块myco,它接收generator函数作为参数

const promise = function(v) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(v)
    }, 1000)
  })
}

const gen = function* () {
  const res1 = yield promise(5)
  console.log(res1)
  const res2 = yield promise(res1 + 5)
  console.log(res2)
  return (res1 + res2)
}

myco(gen)

第一版:

function myco(gen) {
  const it = gen()

  const { value, done } = it.next()

  value.then(res => {
    const { value, done } = it.next(res)

    value.then(res => {
      const { value, done } = it.next(res)

      if (done) {
        // 结束
      }
    })
  })
}

myco(gen)

// 1s后 5
// 2s后 10

以上代码中,第一次调用next(),函数gen开始执行,遇到yield暂停执行并返回它后面的promise,该promiseresolve的时候再次调用next(),函数gen恢复执行,遇到第二个yield再次暂停并返回后面的值,如此往复,直到gen函数执行完毕。理解这个过程,我们可以将myco改成递归的方式

注意:next()的参数会成为上一个yield的返回值,这样的好处在于我们可以不停向函数gen中注入值,这种机制使我们可以以同步的方式来表达异步操作,这样更易于理解

第二版:

function myco(gen) {
  const it = gen()

  function step(v) {
    const { value, done } = it.next(v)

    if (done) return

    value.then(res => {
      step(res)
    })
  }

  step()
}

最后,myco函数会返回一个Promise,该Promise的状态会在gen函数执行完毕的时候resolve

最终版:

function myco(gen) {
  return new Promise(resolve => {
    const it = gen()

    function step(v) {
      const { value, done } = it.next(v)

      if (done) return resolve(value)

      value.then(res => {
        step(res)
      })
    }

    step()
  })
}

myco(gen).then(res => {
  console.log(res)
})

// 1s后 5
// 2s后 10 15

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions