生成器详解

85 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情

前言

在看这篇文章之前,大家需明白什么是迭代器,再学习本篇文章。可点击此链接学习:juejin.cn/post/720593…

一. 什么是生成器?

生成器是ES6中新增的一种函数控制使用方案,可以灵活控制函数执行的行为,其也是一个函数,定义规则为function* XXX(),通过yield关键字来控制函数的执行流程,返回值是一个Generator(生成器)

举例:

function* foo() {
  console.log("函数开始执行~")

  const value1 = 100
  console.log("第一段代码:", value1)
   yield //执行到该位置即暂停

  const value2 = 200
  console.log("第二段代码:", value2)

  const value3 = 300
  console.log("第三段代码:", value3)

  console.log("函数执行结束~")
}

// 调用生成器函数时, 会给我们返回一个生成器对象
const generator = foo()

// 开始执行第一段代码
generator.next()

大家可以在不同的代码位置加入yield 进行测试,如果不写入yield该结果会以默认顺序执行。

1. 生成器传递参数

上方既然可以暂停来分段执行,那么函数是可以传递参数的,我们可以给每个分段来传递参数。

举例:

function* foo(num) {
  console.log("函数开始执行~")

  const value1 = 100 * num
  console.log("第一段代码:", value1)
  const n = yield value1

  const value2 = 200 * n
  console.log("第二段代码:", value2)
  const count = yield value2

  const value3 = 300 * count
  console.log("第三段代码:", value3)
  yield value3

  console.log("函数执行结束~")
  return "123"
}

// 生成器上的next方法可以传递参数
const generator = foo(5)
console.log(generator.next())
// 第二段代码, 第二次调用next的时候执行的
console.log(generator.next(1))
console.log(generator.next(2))

image.png

2. 生成器return 终止 和 throw抛出异常

生成器自带return方法调用即可终止迭代。

举例:

function* foo(num) {
  console.log("函数开始执行~")

  const value1 = 100 * num
  console.log("第一段代码:", value1)
  const n = yield value1

  const value2 = 200 * n
  console.log("第二段代码:", value2)
  const count = yield value2

  const value3 = 300 * count
  console.log("第三段代码:", value3)
  yield value3

  console.log("函数执行结束~")
  return "123"
}

const generator = foo(10)

console.log(generator.next())

// 第二段代码的执行, 使用了return
// 那么就意味着相当于在第一段代码的后面加上return, 就会提前终端生成器函数代码继续执行
console.log(generator.return(15))
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

image.png

当然也可以给生成器函数内部抛出异常: 抛出异常后我们可以在生成器函数中捕获异常; 但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行

举例:

function* foo() {
  console.log("代码开始执行~")

  const value1 = 100
  try {
    yield value1
  } catch (error) {
    console.log("捕获到异常情况:", error)

    yield "abc"
  }

  console.log("第二段代码继续执行")
  const value2 = 200
  yield value2

  console.log("代码执行结束~")
}

const generator = foo()

const result = generator.next()
generator.throw("error message")

image.png

二.生成器代替迭代器

我们知道生成器是一种特殊的迭代器,特殊情况下我们可以用生成器代替迭代器使用。

yield*是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值

举例:

// 生成器来替代迭代器
function* createArrayIterator(arr) {

  // 3.第三种写法 yield*
  // yield* arr

  // 2.第二种写法
  // for (const item of arr) {
  //   yield item
  // }
  // 1.第一种写法
  yield "abc" // { done: false, value: "abc" }
  yield "cba" // { done: false, value: "cba" }
  yield "nba" // { done: false, value: "nba" }
}

const names = ["abc", "cba", "nba"]
const namesIterator = createArrayIterator(names)

console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next()) //{ value: undefined, done: true }
//函数生成器写法
function* createRangeIterator(start, end) {
  let index = start
  while (index < end) {
    yield index++
  }

//第一种写法
  // let index = start
  // return {
  //   next: function() {
  //     if (index < end) {
  //       return { done: false, value: index++ }
  //     } else {
  //       return { done: true, value: undefined }
  //     }
  //   }
  // }
}

const rangeIterator = createRangeIterator(10, 20)
console.log(rangeIterator.next())
console.log(rangeIterator.next())
//类生成器写法
class Classroom {
  constructor(address, name, students) {
    this.address = address
    this.name = name
    this.students = students
  }

  entry(newStudent) {
    this.students.push(newStudent)
  }

  foo = () => {
    console.log("foo function")
  }

  // [Symbol.iterator] = function*() {
  //   yield* this.students
  // }

  *[Symbol.iterator]() {
    yield* this.students
  }
}

const classroom = new Classroom("3幢", "1102", ["abc", "cba"])
for (const item of classroom) {
  console.log(item)
}

三. 生成器处理异步代码

假设模拟网络请求:

// request.js
function requestData(url) {
  // 异步请求的代码会被放入到executor中
  return new Promise((resolve, reject) => {
    // 模拟网络请求
    setTimeout(() => {
      // 拿到请求的结果
      resolve(url)
    }, 2000);
  })
}
  1. 我们可以用promise+generator实现:
function* getData() {
  const res1 = yield requestData("hh")
  const res2 = yield requestData(res1 + "aaa")
  const res3 = yield requestData(res2 + "bbb")
  const res4 = yield requestData(res3 + "ccc")
  console.log(res4)
}

手动执行生成器实现:

const generator = getData()
generator.next().value.then(res => {
  generator.next(res).value.then(res => {
    generator.next(res).value.then(res => {
      generator.next(res)
    })
  })
})

自己可以封装自动执行函数:

function execGenerator(genFn) {
  const generator = genFn()
  function exec(res) {
    const result = generator.next(res)
    if (result.done) {
      return result.value
    }
    result.value.then(res => {
      exec(res)
    })
  }
  exec()
}

// execGenerator(getData)
// execGenerator(getDepartment)

总结

当然我们处理异步代码显然async ,await更好的方式,更简洁,更快速。