JavaScript 系列 - Generator

81 阅读5分钟

生成器函数

function* name([param[, param[, ... param]]]) { statements }

function*  这种声明方式会定义一个生成器函数 (generator function),它返回一个 Generator对象。

Generator对象的 next() 方法被调用时,其内的语句会执行到出现 yield 的位置为止,yield 后紧跟迭代器要返回的值。如果用的是 yield*,则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。

function* anotherGenerator(i) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
}

function* generator(i) {
  yield i;
  yield* anotherGenerator(i); // 移交执行权
  yield i + 10;
}

var gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20

调用 next() 方法时,如果传入了参数,那么这个参数会传给上一条执行的 yield 语句左边的变量

function* gen() {
  yield 10;
  x = yield "foo";
  yield x;
}

var gen_obj = gen();
console.log(gen_obj.next()); // 执行 yield 10,返回 10
console.log(gen_obj.next()); // 执行 yield 'foo',返回 'foo'
console.log(gen_obj.next(100)); // 将 100 赋给上一条 yield 'foo' 的左值,即执行 x=100,返回 100
console.log(gen_obj.next()); // 执行完毕,value 为 undefined,done 为 true

return 后面跟了一个值,那么这个值会作为当前调用 next() 方法返回的 value 值

function* yieldAndReturn() {
  yield "Y";
  return "R"; //显式返回处,可以观察到 done 也立即变为了 true
  yield "unreachable"; // 不会被执行了
}

var gen = yieldAndReturn();
console.log(gen.next()); // { value: "Y", done: false }
console.log(gen.next()); // { value: "R", done: true }
console.log(gen.next()); // { value: undefined, done: true }

Generator

Generator 对象由生成器函数返回并且它符合可迭代协议迭代器协议

生成器函数不能当构造器使用

  • Generator.prototype.next()
  • Generator.prototype.return()
  • Generator.prototype.throw()

迭代协议

可以被任何遵循某些约定的对象来实现

可迭代协议

规定了是否可以迭代

要成为可迭代对象,该对象和原型链上面有 @@iterator 方法,通过常量 Symbol.iterator 访问该属性

迭代器协议

规定了迭代器是什么样子,不一定可以迭代

  • 必须有 next() 方法,无参数或者接受一个参数的函数,参数成为相应 yield 表达式的值
  • next() 方法返回符合 IteratorResult 接口的对象(拥有 done、value、return、throw 属性)

迭代器可迭代(可迭代迭代器),实现 @@iterator 方法,并返回它的 this

// Satisfies both the Iterator Protocol and Iterable
const myIterator = {
  next() {
    // ...
  },
  [Symbol.iterator]() {
    return this;
  },
};
const myIterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  },
};

console.log([...myIterable]); // [1, 2, 3]
const aGeneratorObject = (function* () {
  yield 1;
  yield 2;
  yield 3;
})();

console.log(typeof aGeneratorObject.next);
// "function"——它有 next 方法(返回正确的值),所以它是迭代器

console.log(typeof aGeneratorObject[Symbol.iterator]);
// "function"——它有 @@iterator 方法(返回正确的迭代器),所以它是可迭代的

console.log(aGeneratorObject[Symbol.iterator]() === aGeneratorObject);
// true——它的 @@iterator 方法返回自身(一个迭代器),所以它是一个可迭代的迭代器

异步迭代器和异步可迭代协议

  • 实现 Symbol.asyncIterator 方法它会实现异步可迭代协议
  • 对象实现 next()方法时,它会实现异步迭代器协议

异步生成器函数(但不是同步生成器函数)中的 for await...of 循环和 yield* 是与异步迭代交互的唯一方式。在不是同步迭代的异步迭代对象(即它有 @@asyncIterator() 但没有 @@iterator())上使用 for...of、数组展开等将抛出 TypeError:x is not iterable。

语言和迭代协议之间的交互

内置的可迭代对象

String、Array、TypedArray、Map、Set、Intl.Segments、arguments 对象和一些 DOM 集合类型,NodeList

生成器函数返回生成器对象,它们是可迭代的迭代器。异步生成器函数返回异步生成器对象,它们是异步可迭代的迭代器。

接受可迭代对象的内置 API

  • Map()
  • WeakMap()
  • Set()
  • WeakSet() (en-US)
  • Promise.all()
  • Promise.allSettled()
  • Promise.race()
  • Promise.any()
  • Array.from()
  • Object.groupBy()
  • Map.groupBy()

期待迭代对象的语法

  • for...of 循环、数组和参数扩展、yield* 和数组解构
for (const value of ["a", "b", "c"]) {
  console.log(value);
}
// "a"
// "b"
// "c"

console.log([..."abc"]); // ["a", "b", "c"]

function* gen() {
  yield* ["a", "b", "c"];
}

console.log(gen().next()); // { value: "a", done: false }

[a, b, c] = new Set(["a", "b", "c"]);
console.log(a); // "a"
  • done 为 false,将调用 return 方法
const obj = {
  [Symbol.iterator]() {
    let i = 0;
    return {
      next() {
        i++;
        console.log("Returning", i);
        if (i === 3) return { done: true, value: i };
        return { done: false, value: i };
      },
      return() {
        console.log("Closing");
        return { done: true };
      },
    };
  },
};

const [b] = obj;
// Returning 1
// Closing

const [a, b, c] = obj;
// Returning 1
// Returning 2
// Returning 3
// Already reached the end (the last call returned `done: true`),
// so `return` is not called

for (const b of obj) {
  break;
}
// Returning 1
// Closing

Generator 函数的 this

生成器函数返回一个迭代器不是this对象,迭代器是生成器函数的实例,也继承了生成器函数的 prototype 对象上的方法。

让生成器函数返回一个正常的对象实例,既可以用 next 方法,又可以获得正常的 this

function* F() {
  this.a = 1;
  yield (this.b = 2);
  yield (this.c = 3);
}
var obj = {};
var f = F.call(obj);

f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}

obj.a; // 1
obj.b; // 2
obj.c; // 3

执行的是迭代器对象 f,但是生成的对象实例是 obj,有没有办法将这两个对象统一

示例

自定义可迭代对象

const myIterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  },
};

console.log([...myIterable]); // [1, 2, 3]

简单迭代器

function makeIterator(array) {
  let nextIndex = 0;
  return {
    next() {
      return nextIndex < array.length
        ? {
            value: array[nextIndex++],
            done: false,
          }
        : {
            done: true,
          };
    },
  };
}

const it = makeIterator(["yo", "ya"]);

console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done); // true

无穷迭代器

function idMaker() {
  let index = 0;
  return {
    next() {
      return {
        value: index++,
        done: false,
      };
    },
  };
}

const it = idMaker();

console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2
// ...

使用生成器定义一个可迭代对象

function* makeSimpleGenerator(array) {
  let nextIndex = 0;
  while (nextIndex < array.length) {
    yield array[nextIndex++];
  }
}

const gen = makeSimpleGenerator(["yo", "ya"]);

console.log(gen.next().value); // 'yo'
console.log(gen.next().value); // 'ya'
console.log(gen.next().done); // true

function* idMaker() {
  let index = 0;
  while (true) {
    yield index++;
  }
}

const it = idMaker();

console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2
// ...

使用类定义一个可迭代对象

class SimpleClass {
  #data;

  constructor(data) {
    this.#data = data;
  }

  [Symbol.iterator]() {
    // Use a new index for each iterator. This makes multiple
    // iterations over the iterable safe for non-trivial cases,
    // such as use of break or nested looping over the same iterable.
    let index = 0;

    return {
      // Note: using an arrow function allows `this` to point to the
      // one of `[@@iterator]()` instead of `next()`
      next: () => {
        if (index < this.#data.length) {
          return { value: this.#data[index++], done: false };
        } else {
          return { done: true };
        }
      },
    };
  }
}

const simple = new SimpleClass([1, 2, 3, 4, 5]);

for (const val of simple) {
  console.log(val); // 1 2 3 4 5
}

重写内置的可迭代对象

String 的默认迭代器会逐个地返回字符串

const iterator = someString[Symbol.iterator]();
console.log(`${iterator}`); // "[object String Iterator]"
console.log(iterator.next()); // { value: "h", done: false }
console.log(iterator.next()); // { value: "i", done: false }
console.log(iterator.next()); // { value: undefined, done: true }

@@iterator 重新定义迭代行为

// need to construct a String object explicitly to avoid auto-boxing
const someString = new String("hi");

someString[Symbol.iterator] = function () {
  return {
    // this is the iterator object, returning a single element (the string "bye")
    next() {
      return this._first
        ? { value: "bye", done: (this._first = false) }
        : { done: true };
    },
    _first: true,
  };
};
console.log([...someString]); // ["bye"]
console.log(`${someString}`); // "hi"

异步操作的同步化表达

function* main() {
  var result = yield request("http://some.url");
  var resp = JSON.parse(result);
  console.log(resp.value);
}

function request(url) {
  makeAjaxCall(url, function (response) {
    it.next(response);
  });
}

var it = main();
it.next();

控制流管理

function* longRunningTask(value1) {
  try {
    var value2 = yield step1(value1);
    var value3 = yield step2(value2);
    var value4 = yield step3(value3);
    var value5 = yield step4(value4);
    // Do something with value4
  } catch (e) {
    // Handle any error from step1 through step4
  }
}
scheduler(longRunningTask(initialValue));

function scheduler(task) {
  var taskObj = task.next(task.value);
  // 如果Generator函数未结束,就继续调用
  if (!taskObj.done) {
    task.value = taskObj.value;
    scheduler(task);
  }
}

部署 Iterator 接口

function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i = 0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
  }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
  console.log(key, value);
}

// foo 3
// bar 7

作为数据结构

function* doStuff() {
  yield fs.readFile.bind(null, "hello.txt");
  yield fs.readFile.bind(null, "world.txt");
  yield fs.readFile.bind(null, "and-such.txt");
}
for (task of doStuff()) {
  // task是一个函数,可以像回调函数那样使用它
}