ES6的Generator 函数是个什么函数呢?

1,191 阅读4分钟

前言

在我们学习ES6时,我们知道处理异步的方法有两种,分别是promiseasync-awaitpromise方法很好理解,那就是 ES6 提供出了一个promise构造函数,我们可以直接new一个promise对象出来。我们就可以用这个对象解决异步问题。

这里我们就好奇了,async-await方法虽然也借助了promise对象,但他是怎么通过关键字来阻止代码的执行的呢?

到这里我们的铺垫就结束了,现在就该请出我们今天的主角Generator 函数了。

Generator 函数

首先我们先来给Generator函数做个自我介绍吧。Generator 函数是 ES6 引入的一种特殊函数,它可以通过在函数体内部使用 yield 关键字来定义一个可暂停和可恢复执行的函数。

以 ES6 的原话来讲:

语法来讲,我们可以把Generator函数理解成一个状态机,里面封装了多个内部状态。执行Generator 函数会返回一个遍历器对象,这个对象可以依次遍历 Generator 函数内部的每一个状态。

形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。

说了这么多,可能大家也没明白它究竟是干嘛的吧。那我们直接上一段简单的代码,看看它Generator 函数有什么不一样。

// 函数外部使用 * 来区分与普通函数的不同
function* myGenerator() {
  // 函数体内部使用 yield 关键字定义暂停点
  yield 1;
  yield 2;
  yield 3;
}
const iterator = myGenerator();
console.log(iterator); // 输出:Object [Generator] {}
console.log(iterator.next());  // 输出:{ value: 1, done: false }
console.log(iterator.next());  // 输出:{ value: 2, done: false }
console.log(iterator.next());  // 输出:{ value: 3, done: false }
console.log(iterator.next());  // 输出:{ value: undefined, done: true }

我们来解析解析上述代码,首先它用 * 区分与普通函数的不同, 其次它用 yield 关键字作为函数执行的暂停点。当函数执行遇到了yield关键字它则会暂停执行,最后它需要借助next方法才能将函数继续往下执行,同时next方法返回出一个对象,它的value属性就是当前yield表达式的值,而done属性的值false,表示遍历还没有结束。其代码执行的打印结果也正如注释所示。

image.png

看完上述代码,Generator 函数无非也就是暂停函数的执行,也没看出和async-await有什么关系呀!那接下来,我们将Generator 函数promise结合起来,我们再看看。

与 Promise 结合

我们先来两个函数,一个为Generator 函数,一个为普通函数:

function foo(num) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(num * 2)
    }, 1000)
  })
}
function* gen() {
  const num1 = yield foo(1)
  const num2 = yield foo(num1)
  const num3 = yield foo(num2)
  return num3
}

我们在普通函数中返回了一个promise对象,同时还增加了一个定时器。我们再和之前一样调用它。

  console.log(g.next()); 
  console.log(g.next());
  console.log(g.next()); 
  console.log(g.next()); 

这样调用的话,我们的打印结果如下:

image.png

如上图所示的打印结果,我们next方法每次返回出一个promise对象,而且因为定时器的缘故,这些promise还未resolve就已经被打印了。那如果我们想实现一个像foo函数一样,每次调用都乘2在返回该怎么办呢?我们将调用的方式换一种,如下代码所示:

const g = gen()
const next1 = g.next() // { value: Promise { <pending> }, done: false }
console.log('next1',next1);
next1.value.then(res1 => {
  console.log('res1', res1);
  const next2 = g.next(res1) // { value: Promise { <pending> }, done: false }
  console.log('next2',next2);
  next2.value.then(res2 => {
    console.log('res2',res2);
    const next3 = g.next(res2) // { value: Promise { <pending> }, done: false }
    console.log('next3', next3);
    next3.value.then(res3 => {
      console.log('res3', res3);
      console.log('next3', next3);
      console.log('value', g.next().value);
    })
  })
  
})

我们在看看打印结果: image.png

这不就是我们想要的结果吗?那上面代码是怎么做到的呢?我们来分析分析:

第一步: 声明next1赋值为第一次next()方法返回的对象。next1打印的结果为一个promise对象,在用then去接受promise对象reslove的结果,我们就得到了2这个结果。

第二步:声明next2赋值为第二次next()方法返回的对象。next2打印的结果也为一个promise对象,和next1 不同的就是第二次next方法执行有一个参数res1, 但这个res1不是next的参数,而是函数foo的参数。这样我们就完成了第二次next,同时还得到了4这个结果。

第三步:递归,重复第二次的操作。

这样一步一步的在then方法中套递归,我们就可以完成乘二的操作。我们再来看看async-await的写法

async function gen() {
  const num1 = await foo(1)
  const num2 = await foo(num1)
  const num3 = await foo(num2)
  return num3
}
gen().then(res => {
  console.log(res); //8
})

这样看起来Generator 函数async-await是不是很像,我们在将上述调用方法优化成一个递归调用,我们不就实现了和async-await 一样的效果吗?

所以现在来回答最终的问题:Generator 函数是个什么函数呢

Generator 函数是和 async/await 一样,都提供了一种在异步代码中控制流程的方式的函数。它使我们可以更加灵活地控制函数的执行流程,实现惰性计算、异步编程、流程控制。