JavaScript高级迭代器(Generator)功能

关于迭代器(Iterator)生成器(Generator)的知识,请阅读阮一峰老师的书 ECMAScript 6 入门

下面就开始介绍高级迭代器

1.给迭代器传递参数

给迭代器的next()方法传递参数,则这个参数的值就会替代生成器内部上一条yield语句的返回值。

要点:yield表达式本身没有返回值。yield表达式左边的值等于next方法所带的参数。next方法没有带参数,自然是undefined

function* createIterator() {
    let first = yield 'welcome';
    let second = yield first + 'world';
    yield second + 'hard';
}

//next方法不带参数
let iterator = createIterator();
console.log(iterator.next());// { value: 'welcome', done: false }
console.log(iterator.next());// { value: 'undefinedworld', done: false }
console.log(iterator.next()); // { value: 'undefinedhard', done: false }
console.log(iterator.next());//{ value: undefined, done: true }

//next方法带参数
iterator = createIterator();
console.log(iterator.next());//{ value: 'welcome', done: false }
console.log(iterator.next('hello')); //{ value: 'helloworld', done: false }
console.log(iterator.next('work')); //{ value: 'workhard', done: false }
console.log(iterator.next());//{ value: undefined, done: true }

这里有个特例,第一次调用next()方法时无论传入什么参数都会被丢弃。由于传给next()方法的参数会代替上一次yield的返回值,而在第一次调用next()方法前不会执行任何yield语句,因此在第一次调用next()方法时传递参数是毫无意义的。

2.在迭代器中抛出错误

通过throw()方法当迭代器恢复执行时可令其抛出一个错误。这种主动抛出错误的能力对于异步编程而言至关重要,也能为你提供模拟结束函数执行的两种方法(返回值或抛出错误),从而增强生成器内部的编程弹性。将错误对象抛给throw()方法后,在迭代器继续时其会被抛出。

因此,我们可以在生成器内部通过try-catch代码块来捕获这些错误:

function* createIterator() {
    let first = yield 1;
    let second;
    try {
        second = yield first + 2;
    } catch (ex) {
        second = 6;
    }
    yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next(4)); //{ value: 6, done: false }
console.log(iterator.throw(new Error("Boom"))); //{ value: 9, done: false }
console.log(iterator.next()); //{ value: undefined, done: true }

next()和throw()就像是迭代器的两条指令,调用next()方法命令迭代器继续执行(可能提供一个值),调用throw()方法也会命令迭代器继续执行,但同时会抛出一个错误,在此之后的执行过程取决于生成器内部的代码。

3.生成器返回语句

在生成器中,return表示所有操作已经完成,属性done被设置为true;如果同时提供了相应的值,则属性value会被设置为这个值。

function* createIterator() {
    yield 1;
    return 12;
}
let iterator = createIterator();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 12, done: true }
console.log(iterator.next()); //{ value: undefined, done: true }

上面例子可以得知:通过return语句指定的返回值,只会在返回对象中出现一次,在后续调用返回的对象中,value属性会被重置为undefined;

注意: 展开运算符与for-of循环语句会直接忽略通过return语句指定的任何返回值,只要done一变为true就立即停止读取其他的值。

4.委托生成器

如果在 Generator 函数内部,调用另一个 Generator 函数(这就叫委托生成器),默认情况下是没有效果的。这个就需要用到yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。

function* createNumberIterator() {
    yield 1;
    yield 2;
}

function* createColorIterator() {
    yield 'red';
    yield 'green';
}

function* createCombinedIterator() {
    yield* createNumberIterator();
    yield* createColorIterator();
    yield true;
}

let iterator = createCombinedIterator();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 2, done: false }
console.log(iterator.next()); //{ value: 'red', done: false }
console.log(iterator.next()); //{ value: 'green', done: false }
console.log(iterator.next()); //{ value: true, done: false }
console.log(iterator.next()); //{ value: undefined, done: true }

上面的例子中createNumberIterator函数没有返回值,下面来看下有返回值的Gentertor函数:

function* createNumberIterator() {
    yield 1;
    yield 2;
    return 3;
}

function* createRepeatingIterator(count) {
    for (let i = 0; i < count; i++) {
        yield "repeat";
    }
}

function* createCombinedIterator() {
    let result = yield* createNumberIterator();
    yield* createRepeatingIterator(result);
}

let iterator = createCombinedIterator();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 2, done: false }
console.log(iterator.next()); //{ value: 'repeat', done: false }
console.log(iterator.next()); //{ value: 'repeat', done: false }
console.log(iterator.next()); //{ value: 'repeat', done: false }
console.log(iterator.next()); //{ value: undefined, done: true }

注意: 上面的代码无论通过何种方式调用迭代器的next()方法,数值3永远不会被返回,它只存在于生成器createCombinedIterator()的内部。但如果想输出这个值,则可以额外添加一条yield语句。

yield result;

yield*也可直接应用于字符串,例如yield * “hello”, 此时将使用字符串的默认迭代器。

5.同步任务执行器

由于执行yield语句会暂停当前函数的执行过程并等待下一次调用next()方法,因此你可以创建一个函数,在函数中调用生成器生成相应的迭代器。

  function run(taskDef) {
  //创建一个无使用限制的迭代器
  let task = taskDef();

  //开始执行任务
  let result = task.next();

  //循环调用next()的函数
  function step() {
      //如果任务未完成,则继续执行
      if (!result.done) {
          result = task.next(result.value);
          step();
      }
  }
  step();
}

run(function* (){
    let value = yield 1;
    console.log(value); // 1
    value = yield value +3;
    console.log(value); // 4
});

6.异步任务执行器

以Node.js的异步任务为例

  let fs = require('fs');

  function run(taskDef) {
    //创建一个无使用限制的迭代器
    let task = taskDef();

    //开始执行任务
    let result = task.next();

    //循环调用next()的函数
    function step() {
        //如果任务未完成,则继续执行
        if (!result.done) {
          //这里假设如果result.value是一个函数,那么就一定是异步执行的。
          if (typeof result.value === 'function') {
              //传入一个回调函数作为参数来调用它,回调函数遵循Node.js中有关
              //错误的约定:所有可能的错误放在第一个参数err中,结果放在第二个参数中。
              result.value(function (err, data) {
                  //err存在,说明执行过程产生了错误
                  if (err) {
                      result = task.throw(err);
                  }
                  //如果没有错误产生,data就会被复制给yield左边的变量
                  result = task.next(data);
                  step();
              })
          } else {
              result = task.next(result.value);
              step();
            }
        }
    }
    step();
}

//readFile必须返回一个函数,接收callback
function readFile(filename) {
    return function (callback) {
        fs.readFile(filename, callback);
    }
}

run(function* () {
    let contents = yield readFile("txt.text");
    console.log(contents); //类似这样的结果 <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
});

以上内容,整理自《深入理解ES6》一书

    原文作者:打铁大师
    原文地址: https://www.jianshu.com/p/7de329655c46
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞