generator详解

114 阅读2分钟

generator详解

基本概念

Generator(生成器)是一个可以 暂停 恢复执行 的函数。

function* gen() {
	yield 1;
	yield 2;
	return 3
}
// 调用
const g = gen()
g.next(); // { value:1, done:false }
g.next(); // { value:2, done:false }
g.next(); // { value:3, done:true }
g.next(); // { value:undefined, done:true }

生成器的定义与传统的函数非常相似,不同的地方在于生成器的关键字是function*​,带了星号*​,并且使用了关键字yield来指定生成器的执行步骤。

yield可以看成是一个暂停器,生成器执行到 yield​ 的时候,就会停止执行并且将yield​后面的值作为返回值,并记录下当前运行的上下文,等待下一次调用next()方法时恢复上下文和yield后面的值,并继续执行,直到生成器执行结束。

next()的机制能够让生成器恢复执行,每一次调用,都会让状态机推进一步。

next还能向生成器内部传值。

function* gen() {
  const x = yield '请输入 x';
  const y = yield '请输入 y';
  return x + y;
}

const g = gen();

console.log(g.next(111111));       // pause: yield '请输入 x'
console.log(g.next(10));     // 10 赋给 x
console.log(g.next(20));     // 20 赋给 y → return 30

以上代码中,可以看出,在第一次调用next时,next传参是没用的,只有从第二次开始传参才有用。需要注意,先进行yield,再赋值。

通常情况下,我们可以使用生成器来处理状态化的问题,在文件读写、网络请求等场景中都可以使用。

使用场景
  1. 代替回调的异步流程控制,如今基本使用async/await 的方式。
function* task() {
  const a = yield ajax('/api/a');
  const b = yield ajax('/api/b?a=' + a);
  return b;
}
  1. 实现可中断的长任务(节流 / 分帧),可以结合requestIdleCallback() 做分片处理
function* bigTask() {
  for (let i=0;i<100000;i++){
    doSomething(i);
    if (i % 100 === 0) yield; // 让出主线程
  }
}
  1. 创建数据序列(迭代器)
function* count() {
  let i = 0;
  while (true) yield i++;
}
  1. 实现状态机
function* fsm() {
  while (true) {
    yield 'state1';
    yield 'state2';
    yield 'state3';
  }
}
  1. yield:组合多个 generator*
function* a() { yield 1; yield 2; }
function* b() { yield 3; yield 4; }

function* all() {
  yield* a();
  yield* b();
}

console.log([...all()]) // [1,2,3,4]

Generator VS async/await 的关系

async/await 是对 generator 的终极封装。

async/await 的内部原理核心就是:

Generator + 自动执行器 + Promise = async function

Generator 本身就能写 async/await 的效果:

function* gen() {
  const a = yield Promise.resolve(1);
  const b = yield Promise.resolve(a + 1);
  return b;
}

async/await 多了一个“自动执行器”:

function run(g) {
  const it = g();
  function next(v) {
    const {value, done} = it.next(v);
    if (done) return value;
    value.then(res => next(res));
  }
  next();
}

async/await的终极实现:

function run(genFn) {
  const gen = genFn(); // 拿到 generator 实例

  return new Promise((resolve, reject) => {
    function step(nextF, arg) {
      let next;
      try {
        next = nextF.call(gen, arg); // 调用 next() 或 throw()
      } catch (err) {
        return reject(err); // generator 内部报错
      }

      // 结束了
      if (next.done) {
        return resolve(next.value);
      }

      // value 必须是 Promise
      Promise.resolve(next.value)
        .then(v => step(gen.next, v))      // 成功 → 注入 generator
        .catch(err => step(gen.throw, err)); // 失败 → throw 进入 generator
    }

    step(gen.next); // 启动 generator
  });
}