今天来学学Generator函数

225 阅读4分钟

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

问题

面试官问:你能讲讲什么是async函数吗?

面试者:巴拉巴拉一大堆。。。,它是Generator函数的语法糖。

面试官: 那你能讲讲Generator函数吗,为什么说是它的语法糖?

面试者: 。。。

以上这个场景纯属虚构,可能会有雷同。

今天咱们来学学Generator函数,如果遇到上面这个场景我们就可以对面试官侃侃而谈。

Generator函数

定义

Generator函数和普通函数不同,普通函数执行时函数的内容会全部执行完,一般不可以在中途中断它(除非报错,这种一般不考虑),而调用Generator函数并不会执行函数,而是返回一个遍历器对象,只有调用它的next方法,函数才会执行,如果这个函数里面有多个状态(通过yield关键字定义),需要执行多个next方法。

等同于一个同步函数,后一个状态只有等前一个状态执行后才能执行,以此类推。

语法

function* Fn() {
  console.log(1)
  yield '答案cp3'
  console.log(2)
  yield '18'
  console.log(3)
  return 'good boy'
}
var fn = Fn()


fn.next() // 打印1, 返回{value: '答案cp3', done: false}
fn.next() // 打印2, 返回{value: '18', done: false}
fn.next() // 打印3, 返回{value: 'good boy', done: true}
fn.next() // {value: undefined, done: true}
fn.next() // {value: undefined, done: true}

它的定义方式是通过function加上*号,然后内部是使用yield定义状态,前一个状态执行后,后一个状态才可以执行,顺序执行。

调用Generator函数,并不会执行函数,而是返回遍历器对象,原型上常用的有三个方法,next方法,return方法,throw方法。

next方法一般是和yield关键字配合,执行一次next方法,会去找第一个yield关键字,执行它之前的代码,再执行一次next方法,会去找第二个yield关键字,执行两个yield之间的代码,依次类推,直到结尾。

next方法 和 yield

执行next方法会返回一个对象,包含value属性和done属性。

yield 右边一般跟着表达式或者值,它们的值会当作执行next方法后返回的value属性,如果没有值,则是undefined

done属性是表明当前是否函数已经执行结束,如果最后有return, return的值就是最后的value值。

function* Fn() {
  yield '答案cp3'
  yield 
  return 'good boy'
}
var fn = Fn()
fn.next() // {value: '答案cp3', done: false}
fn.next() // {value: undefined, done: false}
fn.next() // {value: 'good boy', done: true} 

yield执行时默认是返回undefined的,如果通过next传参,参数是yield返回的值。

function* Fn() {
  var num = yield 1+2
  yield  num * 2
  return 
}
var fn = Fn()
fn.next() // value: 3
fn.next() // value: NaN  undefined * 2 等于NaN


var fn = Fn()
fn.next() // value: 3
fn.next(10) // value: 20

return方法

因为正常是需要next方法一步一步执行到结束,如果想中间中断的话,可以使用return方法。 支持传参,参数是返回的对象的value值。

例子如下:

function* Fn() {
  yield '答案cp3'
  yield 
  return 'good boy'
}
var fn = Fn()
fn.next()   // {value: '答案cp3', done: false}
fn.return('end') // {value: 'end', done: true}  中止了,done是true
fn.next()  // {value: undefined, done: true}

throw方法

可以通过throw方法在Generator函数中抛出错误,一旦抛出错误,如果没有try-catch捕获,Generator函数就中止了。 如果捕获了,会额外执行一次next方法。

function* Fn() {
     yield 1
     yield 2
     return 3
}
var fn = Fn()
fn.next()   // {value: 1, done: false}
fn.throw('i am error message') //  Uncaught i am error message    fn: {value: undefined, done: true}


function* Fn() {
    try{
      yield 1
    } catch (e) {
       console.log(e)  // i am error message
    }
    yield 2
}
var fn = Fn()
fn.next()   // {value: 1, done: false}
fn.throw('i am error message')  // {value: 2, done: true}

如果需要捕获throw方法,则需要next方法先执行一遍。

function* Fn() {
    try{
      yield 1
    } catch (e) {
       console.log(e)  // i am error message
    }
    yield 2
}
var fn = Fn()
fn.throw('i am error message')

for-of,扩展运算符,Array.from,解构等

for-of可以直接遍历Generator函数,因为Generator函数执行时返回的是Iterator对象,非常方便。

function* Fn() {
    yield 1
    yield 2
    yield 3
    return 4
}
var fn = Fn()

for(let v of fn) {
  console.log(v) // 1 2 3
}
// 扩展运算符也行
[...fn]

// 解构也行
[one, two] = fn

注意,结果是不包含return的返回值的。

补充

Generator函数无法使用new 实例化

function* Fn() {
    yield 1
    yield 2
    yield 3
    return 4
}
new Fn() //  Fn is not a constructor

例外,还有一个yield* fn(), fn是generator函数, 代表的generator函数可以嵌套generator函数。

如下:

function* Fn1() {
    yield 1
    yield 2
}
function* Fn2() {
    yield* Fn1()
    yield 3
    yield 4
}
[...Fn2()] // [1,2,3,4]

参考文献

Generator 函数的语法