generator基础知识

发布时间 2023-11-27 16:45:29作者: 柯基与佩奇

Generator 是 ES6 中新增的语法,和 Promise 一样,都可以用来异步编程。Generator 函数可以说是 Iterator 接口的具体实现方式。Generator 最大的特点就是可以控制函数的执行。

  • function* 用来声明一个函数是生成器函数,它比普通的函数声明多了一个*\*的位置比较随意可以挨着 function 关键字,也可以挨着函数名
  • yield 产出的意思,这个关键字只能出现在生成器函数体内,但是生成器中也可以没有 yield 关键字,函数遇到 yield 的时候会暂停,并把 yield 后面的表达式结果抛出去
  • next 作用是将代码的控制权交还给生成器函数
function* foo(x) {
  let y = 2 * (yield x + 1);
  let z = yield y / 3;
  return x + y + z;
}
let it = foo(5);
console.log(it.next()); // => {value: 6, done: false}
console.log(it.next(12)); // => {value: 8, done: false}
console.log(it.next(13)); // => {value: 42, done: true}

上面这个示例就是一个 Generator 函数,来分析其执行过程:

  1. 首先 Generator 函数调用时它会返回一个迭代器
  2. 当执行第一次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6
  3. 当执行第二次 next 时,传入的参数等于上一个 yield 的返回值,如果不传参,yield 永远返回 undefined。此时 let y = 2 * 12,所以第二个 yield 等于 2 * 12 / 3 = 8
  4. 当执行第三次 next 时,传入的参数会传递给 z,所以 z = 13, x = 5, y = 24,相加等于 42

yield 实际就是暂缓执行的标示,每执行一次 next(),相当于指针移动到下一个 yield 位置

总结一下,Generator 函数是 ES6 提供的一种异步编程解决方案。通过 yield 标识位和 next()方法调用,实现函数的分段执行

遍历器对象生成函数,最大的特点是可以交出函数的执行权

  • function 关键字与函数名之间有一个星号;
  • 函数体内部使用 yield 表达式,定义不同的内部状态;
  • next 指针移向下一个状态

这里可以说说 Generator 的异步编程,以及它的语法糖 async 和 awiat,传统的异步编程。ES6 之前,异步编程大致如下

  • 回调函数
  • 事件监听
  • 发布/订阅

传统异步编程方案之一:协程,多个线程互相协作,完成异步任务。

// 使用 * 表示这是一个 Generator 函数
// 内部可以通过 yield 暂停代码
// 通过调用 next 恢复执行
function* test() {
  let a = 1 + 2;
  yield 2;
  yield 3;
}
let b = test();
console.log(b.next()); // >  { value: 2, done: false }
console.log(b.next()); // >  { value: 3, done: false }
console.log(b.next()); // >  { value: undefined, done: true }

从以上代码可以发现,加上 * 的函数执行后拥有了 next 函数,也就是说函数执行后返回了一个对象。每次调用 next 函数可以继续执行被暂停的代码。以下是 Generator 函数的简单实现

// cb 也就是编译过的 test 函数
function generator(cb) {
  return (function () {
    var object = {
      next: 0,
      stop: function () {},
    };

    return {
      next: function () {
        var ret = cb(object);
        if (ret === undefined) return { value: undefined, done: true };
        return {
          value: ret,
          done: false,
        };
      },
    };
  })();
}
// 如果使用 babel 编译后可以发现 test 函数变成了这样
function test() {
  var a;
  return generator(function (_context) {
    while (1) {
      switch ((_context.prev = _context.next)) {
        // 可以发现通过 yield 将代码分割成几块
        // 每次执行 next 函数就执行一块代码
        // 并且表明下次需要执行哪块代码
        case 0:
          a = 1 + 2;
          _context.next = 4;
          return 2;
        case 4:
          _context.next = 6;
          return 3;
        // 执行完毕
        case 6:
        case "end":
          return _context.stop();
      }
    }
  });
}