Skip to content

generator

常规函数只会返回一个单一值(或者不返回任何值)。 而 generator 可以按需一个接一个地返回(“yield”)多个值。它们可与 iterable 完美配合使用,从而可以轻松地创建数据流。

  1. generator 函数
JavaScript
	function* generateSequence() {
	  yield 1;
	  yield 2;
	  return 3;
	}
	let generator = generateSequence();
	let one = generator.next();
	alert(JSON.stringify(one)); // {value: 1, done: false}
	//再次调用{value: 2, done: false}
	//第三次调用{value: 3, done: true}
  1. generator 是可迭代的
JavaScript
function* generateSequence() {
	  yield 1;
	  yield 2;
	  return 3;
	}
	
let generator = generateSequence();

for(let value of generator) {
  alert(value); // 1,然后是 2
}
  1. 使用 generator 进行迭代
JavaScript
	let range = {
	  from: 1,
	  to: 5,
	
	  // for..of range 在一开始就调用一次这个方法
	  [Symbol.iterator]() {
	    // ...它返回 iterator object:
	    // 后续的操作中,for..of 将只针对这个对象,并使用 next() 向它请求下一个值
	    return {
	      current: this.from,
	      last: this.to,
	
	      // for..of 循环在每次迭代时都会调用 next()
	      next() {
	        // 它应该以对象 {done:.., value :...} 的形式返回值
	        if (this.current <= this.last) {
	          return { done: false, value: this.current++ };
	        } else {
	          return { done: true };
	        }
	      }
	    };
	  }
	};
	
	// 迭代整个 range 对象,返回从 `range.from` 到 `range.to` 范围的所有数字
	alert([...range]); // 1,2,3,4,5
1. generator 可以永远产出(yield)值
  1. generator 组合
    1. generator 组合(composition)是 generator 的一个特殊功能,它允许透明地(transparently)将 generator 彼此“嵌入(embed)”到一起。
JavaScript
	重用它来生成一个更复杂的序列
	- 首先是数字 `0..9`(字符代码为 4857),
	- 接下来是大写字母 `A..Z`(字符代码为 6590
	- 接下来是小写字母 `a...z`(字符代码为 97122
	
	function* generateSequence(start, end) {
	  for (let i = start; i <= end; i++) yield i;
	}
	
	function* generatePasswordCodes() {
	
	  // 0..9
	  yield* generateSequence(48, 57);
	
	  // A..Z
	  yield* generateSequence(65, 90);
	
	  // a..z
	  yield* generateSequence(97, 122);
	
	}
	
	let str = '';
	
	for(let code of generatePasswordCodes()) {
	  str += String.fromCharCode(code);
	}
	
	alert(str); // 0..9A..Za..z
  1. “yield” 是一条双向路
    1. yieId不仅可以向外返回结果,而且还可以将外部的值传递到 generator 内。
JavaScript
	function* gen() {
	  // 向外部代码传递一个问题并等待答案
	  let result = yield "2 + 2 = ?"; // (*)
	
	  alert(result);
	}
	
	let generator = gen();
	
	let question = generator.next().value; // <-- yield 返回的 value
	
	generator.next(4); // --> 将结果传递到 generator 中

1. 第一次调用 `generator.next()` 应该是不带参数的(如果带参数,那么该参数会被忽略)。它开始执行并返回第一个 `yield "2 + 2 = ?"` 的结果。此时,generator 执行暂停,而停留在 `(*)` 行上。
2. 然后,正如上面图片中显示的那样,`yield` 的结果进入调用代码中的 `question` 变量。
3. 在 `generator.next(4)`,generator 恢复执行,并获得了 `4` 作为结果:`let result = 4`
  1. generator.throw
    1. 要向 yield 传递一个 error,我们应该调用 generator.throw(err)
JavaScript
function* gen() {
  try {
    let result = yield "2 + 2 = ?"; // (1)

    alert("The execution does not reach here, because the exception is thrown above");
  } catch(e) {
    alert(e); // 显示这个 error
  }
}

let generator = gen();

let question = generator.next().value;

generator.throw(new Error("The answer is not found in my database")); // (2)
  1. generator.return
    1. generator.return(value) 完成 generator 的执行并返回给定的 value
JavaScript
	function* gen() {
	  yield 1;
	  yield 2;
	  yield 3;
	}
	
	const g = gen();
	
	g.next();        // { value: 1, done: false }
	g.return('foo'); // { value: "foo", done: true }
	g.next();        // { value: undefined, done: true }

异步迭代和 generator

异步迭代允许我们对按需通过异步请求而得到的数据进行迭代

  1. 回顾可迭代对象
JavaScript
	let range = {
	  from: 1,
	  to: 5
	};

	向对象 range 添加 迭代能力。通过使用一个名为 `Symbol.iterator` 的特殊方法来实现
	
let range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() { // 在 for..of 循环开始时被调用一次
    return {
      current: this.from,
      last: this.to,

      next() { // 每次迭代时都会被调用,来获取下一个值
        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

for(let value of range) {
  alert(value); // 1,然后 2,然后 3,然后 4,然后 5
}
  1. 异步可迭代对象
    1. 当值是以异步的形式出现时,例如在 setTimeout 或者另一种延迟之后,就需要异步迭代。
    2. 最常见的场景是,对象需要发送一个网络请求以传递下一个值
    3. 使对象异步迭代
      1. 使用 Symbol.asyncIterator 取代 Symbol.iterator
      2. next() 方法应该返回一个 promise(带有下一个值,并且状态为 fulfilled)。
        • 关键字 async 可以实现这一点,我们可以简单地使用 async next()
      3. 我们应该使用 for await (let item of iterable) 循环来迭代这样的对象。
        • 注意关键字 await
JavaScript
let range = {
  from: 1,
  to: 5,

  [Symbol.asyncIterator]() { // (1)
    return {
      current: this.from,
      last: this.to,

      async next() { // (2)

        // 注意:我们可以在 async next 内部使用 "await"
        await new Promise(resolve => setTimeout(resolve, 1000)); // (3)

        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

(async () => {

  for await (let value of range) { // (4)
    alert(value); // 1,2,3,4,5
  }

})()
  1. Iterator 和异步 iterator 之间差异的表格:
Iterator异步 iterator
提供 iterator 的对象方法
Symbol.iteratorSymbol.asyncIterator
next() 返回的值是任意值Promise
要进行循环,使用for..offor await..of
Spread 语法 ... 无法异步工作:需要常规的同步 iterator 的功能,无法与异步 iterator 一起使用。
  1. 回顾 generator
JavaScript
	function* generateSequence(start, end) {
	  for (let i = start; i <= end; i++) {
	    yield i;
	  }
	}
	
	for(let value of generateSequence(1, 5)) {
	  alert(value); // 1,然后 2,然后 3,然后 4,然后 5
	}
  1. 异步 generator (finally)
    1. 对于大多数的实际应用程序,当我们想创建一个异步生成一系列值的对象时,我们都可以使用异步 generator。
    2. 在 function* 前面加上 async。这即可使 generator 变为异步的。
JavaScript
	async function* generateSequence(start, end) {
	  for (let i = start; i <= end; i++) {
	    // 哇,可以使用 await 了!
	    await new Promise(resolve => setTimeout(resolve, 1000));
	    yield i;
	  }
	}
	(async () => {
	  let generator = generateSequence(1, 5);
	  for await (let value of generator) {
	    alert(value); // 1,然后 2,然后 3,然后 4,然后 5(在每个 alert 之间有延迟)
	  }
	})();




异步的可迭代对象 range
let range = {
  from: 1,
  to: 5,

  // 这一行等价于 [Symbol.asyncIterator]: async function*() {
  async *[Symbol.asyncIterator]() {
    for(let value = this.from; value <= this.to; value++) {

      // 在 value 之间暂停一会儿,等待一些东西
      await new Promise(resolve => setTimeout(resolve, 1000));

      yield value;
    }
  }
};

(async () => {

  for await (let value of range) {
    alert(value); // 1,然后 2,然后 3,然后 4,然后 5
  }

})();




实际的例子:分页的数据
async function* fetchCommits(repo) {
  let url = `https://api.github.com/repos/${repo}/commits`;

  while (url) {
    const response = await fetch(url, { // (1)
      headers: {'User-Agent': 'Our script'}, // github 需要任意的 user-agent header
    });

    const body = await response.json(); // (2) 响应的是 JSON(array of commits)

    // (3) 前往下一页的 URL 在 header 中,提取它
    let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/);
    nextPage = nextPage?.[1];

    url = nextPage;

    for(let commit of body) { // (4) 一个接一个地 yield commit,直到最后一页
      yield commit;
    }
  }
}