Generator

Generator 基本用法

Promise 解决了异步嵌套的问题,ES6 又推出了 Generator 函数,这是一个新的函数类型,也叫做生成器

来看例子:

let x = 1

function a() {
  x++
  b()
  console.log(x)
}
a()

function b() {
  x++
}

输出结果是什么?毫无疑问会是 3

如果我把 b() 这条调用函数 b 的行为取消,那结果应该是 2,有没有方法可以做到在取消调用函数 b 之后,仍然可以得到 3 的结果呢?

答案就是:Generator

let x = 1

function* a() {
  x++
  yield
  console.log(x)
}

function b() {
  x++
}

可以看到 Generator 函数的声明相比普通函数多了一个*,当然也可以写成function *a()function * a()甚至是function*a(),这几种写法没有本质上的区别,只是风格的不同而已。

函数里有一个yield命令,可以暂时把它理解为'暂停'。

然后我执行下面的命令:

let c = a()
c.next()
console.log(x) //2
b()
c.next() //3
  1. 在上面的代码中,执行了let c = a(),这个操作的意义在于构造了一个迭代器 c,这个迭代器会控制生成器 a 中代码的执行。
  2. 第一个c.next()使得生成器*a正式开始执行,它运行了生成器中第一行代码x ++
  3. 接下来执行yield,这个时候生成器暂停了,也就是说第一个c.next()命令已经执行结束,这时候 *a仍然处于运行状态,只不过它暂停了而已。
  4. 此时控制台输出x = 2
  5. 调用函数 b 使得x ++
  6. 第二个c.next()继续执行下面的命令,控制台输出x = 3

这样,整个函数就算是执行完毕了,当然,如果后面的代码中还有yield,那么需要执行第三个c.next()才能执行完整个函数。
生成器达到了不需要在函数内调用 b,就可以得到x = 3的效果,它可以一次或者多次的进行停止,这取决于yield的数量。

Generator 函数和普通函数一样可以接收参数,但就像上面的例子一样,Generator 并没有像普通函数一样运行。

next()看上去像是执行两个yield之间的操作(如果有上一个yield和下一个yield的话),但实际上它也需要完成上一个被暂停的yield表达式。

看下面的例子:

let x = 2
function* a() {
  let y = x * (yield)
  console.log(y)
}
let b = a()

同样是创建了一个生成器 a,构造了一个迭代器 b,不同的是yield表达式写在了执行语句之内。

b.next()
b.next(6) //12

第一个next执行yield之前的语句,直到yield暂停,第二个next6 传给正在等待的yield,这样 6 就被传入了生成器,最后控制台输出y = 12

现在有一个问题,为什么next执行的次数要比yield多一次呢?
因为第一个next总是启动一个生成器,并运行到第一个yield处,而第二个next总是调用完成第一个被暂停的yield表达式,第三个next完成第二个yield...
什么意思呢?
next方法会返回一个对象,这个对象的value属性就是当前yield表达式的值,也就是当前内部状态值,对象还有一个属性是done,表示迭代是否结束。上述代码在执行第一个next的时候就返回了一个对象:

截图未命名.jpg

这个时候因为yield没有值,所以是undefined

执行第二个next的时候:

截图未命名.jpg

还是undefined,因为现在value并不是第一个yield的值了,那是什么值呢?
console.log(y)改成return(y)试试:

截图未命名.jpg

原来最后一个next返回的对象value值是最后return的值,如果生成器中没有return,那么会自动生成一个隐式的return返回undefined,这样就能解释nextyield多一个的原因了。