Generator

1. Generator 函数介绍与使用

执行 Generator 函数会返回一个遍历器对象(iterator),可以依次遍历函数内部的每一个状态。

形式上,Generator 函数是一个普通函数,但有两个特征:function* 和 yield,例如:

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

const hw = helloWorldGenerator();
hw.next(); // { value: 'hello', done: false }
hw.next(); // { value: 'world', done: false }
hw.next(); // { value: 'ending', done: true }
hw.next(); // { value: undefined, done: true }
  • done 判断是否存在下一个状态,value 对应状态值。
  • yield 表达式本身没有返回值,或者说总是返回 undefined。
  • 通过 next 方法可以传一个参数,该参数会被当作上一个 yield 表达式的返回值。
function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}
var b = foo(5);
b.next();      // { value: 6,  done: false }
b.next(12);    // { value: 8,  done: false }
b.next(13);    // { value: 42, done: true  }

由于 Generator 返回 Iterator,所以可以用 for...of 遍历:

function* foo() {
  yield 1; yield 2; yield 3; yield 4; yield 5; return 6;
}
for (let v of foo()) console.log(v); // 1 2 3 4 5

原生对象没有遍历接口,可以通过 Generator 函数为它加上 Iterator 接口:

function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj);
  for (let propKey of propKeys) yield [propKey, obj[propKey]];
}
for (let [key, value] of objectEntries({ first: 'Jane', last: 'Doe' })) {
  console.log(`${key}: ${value}`); // first: Jane / last: Doe
}

2. 异步解决方案

  • 回调函数:把第二段任务单独写在回调里,会形成"回调地狱"。
fs.readFile('/etc/fstab', function (err, data) {
  if (err) throw err;
  console.log(data);
  fs.readFile('/etc/shells', function (err, data) { /* ... */ });
});
  • Promise:把嵌套回调改成链式调用,缓解回调地狱,但代码冗杂、语义化不强。

  • Generator:yield 暂停、next 恢复,适合把异步任务同步化表达:

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
};
  • async/await:上述 Generator 的语法糖,更简洁、语义更强,是当前异步编程的终极方案。
const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
};

3. Generator / Promise / async-await 区别

维度PromiseGeneratorasync/await
写法.then() 链式function* + yieldasync + await
自动执行是否(需 co 等执行器)是
错误捕获.catch()try/catchtry/catch
语义化一般较绕最强
适用简单异步链流式控制几乎所有异步场景

4. 使用场景

  • 异步解决方案(历史地位,现在已被 async/await 取代大部分场景)。
  • 给对象加上 Iterator 接口(for...of)。
  • 状态机 / 流式控制(如加载/卸载 UI):
function* loadUI() {
  showLoadingScreen();
  yield loadUIDataAsynchronously();
  hideLoadingScreen();
}
var loader = loadUI();
loader.next(); // 加载
loader.next(); // 卸载

来源整理自:vue3js.cn 面试官系列