欢迎来到好多视频第239期,这期咱们先通过一个简单的例子,聊清楚 JS 的 generator 的基本原理,然后再看看用 generator 的方式做异步操作有多酷,是滴,比 callback/Promise/async-await 这些都酷。

定义 generator 函数

先来定义一个 generator 函数

function* myGen() {
  console.log('started...')
  const result = yield 1
  console.log('result', result)
}

let i = myGen()

let valueFromGen = i.next().value
let peter = valueFromGen * 100
i.next(peter)

有人开玩笑说 generator 函数就是一个超级明星函数,因为声明的时候会带一个星号。这里 myGen 就是一个 generator 函数,除了星号,generator 另外一个最明显的特征就是会使用 yield 关键字。yield 之后,result 到底何时能后被赋值,值是多少,都不是 generator 自己决定的。可以说 genrator 是一个比较傻的函数,不能独自用来完成某个任务,必须要配合外部代码才行。

外部代码中,执行 myGen() ,这时 myGen 里面的语句其实一句都不会被执行,但是会返回一个迭代器 iterator 。执行 iterator.next() ,generator 函数中的语句才开始执行,但是一执行到 yield 关键字,执行就会停止,所以 generator 函数还有另外一个称呼,叫“可暂停的函数”。yield 会把自己右边的表达式交给外部逻辑,然后程序就停下了,等号左边的 result 常量是不会被赋值的。

可以说外部代码跟 generator 有一个输入和输出的关系,通过 iterator.next().value 外部代码可以拿到 yield 右侧的内容,这里就是 1 这个值,这个算是输入,这里把它赋值到 valueFromGen 变量。那外部逻辑的输出如何返回给 generator 函数呢?

我们把 valueFromGen 乘以100,赋值给 peter 。下面运行 iterator.next(peter) ,这样 generator 中的语句会继续执行,拿到外部代码输出的 peter ,赋值给 result ,然后打印出来,如果后面还有 yield 就会再次暂停,不然就一直执行完 generator 中所有的语句。

终端中,执行 node index.js ,可以看到 result 最终被赋值为 100 。

实现异步请求

不实际使用一下,看不出 generator 这么怪的语法是干嘛用的。

const axios = require('axios')

function* myGen() {
  const result = yield axios.get('https://jsonplaceholder.typicode.com/posts/1')
  console.log('result', result)
}

const run = gen => {
  let i = gen()

  let myPromise = i.next().value
  myPromise.then(
    res => {
      i.next(res.data)
    }
  )
}

run(myGen)

安装并导入 axios ,然后 myGen 中 yield 后面发出 get 请求到好心的 typicode 提供的一个公共 API 。下面打印出返回结果。如果没有 yield ,那这里 result 就是一个 Promise ,肯定拿不到请求回来的数据的。

下面把外部逻辑封装到一个 run 函数中,把 generator 作为参数传入,首先拿到迭代器,然后 i.next.value 就能拿到这个 Promise 了,执行 .then ,等请求数据返回了,执行 i.next ,把返回数据作为参数传入。这样,最终实际赋值给 generator 中的 result 变量的就是返回数据了。

到终端中,运行一下,果然打印出了返回数据。这样,只要把 run 函数封装到库文件中,以后写异步请求的代码,都可以直接在 generator 里面,写成了类似同步样式,真的是非常酷。

其实,上面 run 函数实现的功能是如此的常用,高手们已经实现了类似功能的库可以直接使用了,例如 co ,或者 redux-saga

参考