Generator

573 阅读3分钟

这是我参与更文挑战的第12天,活动详情查看:更文挑战

Generator生成器,也叫做生成器函数,它本质上是函数,但是它可以返回多次。Generator是ES6标准引入的新的数据类型。

概念

生成器是一个普通函数,特征在于:

  • function关键字与函数名之间有一个星号(*),function*
  • 函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
  • 函数体内部使用return,可以返回给定的值,并且终结遍历Generator函数。
function* foo(x) {
    yield x + 1;
    yield x + 2;
    return x + 3;
    yield x + 4;
}

生成器函数与普通函数对比:

  • 都是通过括号()执行。
  • 调用生成器函数后,函数并不立即执行,返回的不是函数运行结果,而是一个迭代器对象。
  • 通过迭代器对象next方法分段执行函数,获取函数内部yield产出值。
const r = foo(0);

r.next();
// { value: 1, done: false }

r.next();
// { value: 2, done: false }

r.next();
// { value: 3, done: true }

r.next();
// { value: undefined, done: true } 被return终结了

r.next();
// { value: undefined, done: true }

yield表达式

yield只能用在生成器函数中,在普通函数或者其它地方使用都会报语法错误。 yield在生成器内部表示暂停标志,对调用的地方来说表示产出值。

yield表达式与return语句既有相似之处,也有区别。

  • 相似之处在于,都能返回紧跟在语句后面的那个表达式的值。
  • 区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。
  • 一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式。
  • 正常函数只能返回一个值,因为只能执行一次returnGenerator 函数可以返回一系列的值,因为可以有任意多个yield

next方法

yield表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

Generator函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值。也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。

function* dataConsumer () {
  console.log('Started');
  console.log(`1. ${yield}`);
  console.log(`2. ${yield}`);
  return 'result';
}

// 等效代码
function* dataConsumer () {
  console.log('Started');
  let result = yield;
  console.log(`1. ${result}`);
  result = yield;
  console.log(`2. ${result}`);
  return 'result';
}

let genObj = dataConsumer();
genObj.next(); // {value: undefined, done: false}
// Started
genObj.next('a'); // {value: undefined, done: false}
// 1. a
genObj.next('b'); // {value: "result", done: false}
// 2. b

for...of循环

for...of循环可以自动遍历 Generator函数运行时生成的Iterator对象,且此时不再需要调用next方法。

function* foo () {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (const v of foo()) {
  console.log(v);
}

注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中。

但是yield 5之后的代码是会被执行。

异常

Generator函数返回的遍历器对象都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。

const g = function* () {
    try {
        yield;
    } catch(e){
        console.log('内部捕获', e);// 捕获i对象的第一个throw
    }
};

const  i = g();
i.next();

try {
    i.throw('a');// 在函数体外抛出错误,在函数体内捕获错误
    i.throw('b');// 在函数体外抛出错误,在函数体外捕获错误
} catch (e) {
    console.log('外部捕获', e);
}

// 结果
// 内部捕获 a
// 外部捕获 b