generator
常规函数只会返回一个单一值(或者不返回任何值)。 而 generator 可以按需一个接一个地返回(“yield”)多个值。它们可与 iterable 完美配合使用,从而可以轻松地创建数据流。
- 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}
- generator 是可迭代的
JavaScript
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1,然后是 2
}
- 使用 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)值
- generator 组合
- generator 组合(composition)是 generator 的一个特殊功能,它允许透明地(transparently)将 generator 彼此“嵌入(embed)”到一起。
JavaScript
重用它来生成一个更复杂的序列
- 首先是数字 `0..9`(字符代码为 48…57),
- 接下来是大写字母 `A..Z`(字符代码为 65…90)
- 接下来是小写字母 `a...z`(字符代码为 97…122)
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
- “yield” 是一条双向路
- 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`。
- generator.throw
- 要向
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)
- generator.return
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
异步迭代允许我们对按需通过异步请求而得到的数据进行迭代
- 回顾可迭代对象
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
}
- 异步可迭代对象
- 当值是以异步的形式出现时,例如在
setTimeout
或者另一种延迟之后,就需要异步迭代。 - 最常见的场景是,对象需要发送一个网络请求以传递下一个值
- 使对象异步迭代
- 使用
Symbol.asyncIterator
取代Symbol.iterator
。 next()
方法应该返回一个promise
(带有下一个值,并且状态为fulfilled
)。- 关键字
async
可以实现这一点,我们可以简单地使用async next()
。
- 关键字
- 我们应该使用
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
}
})()
- Iterator 和异步 iterator 之间差异的表格:
Iterator | 异步 iterator | ||
---|---|---|---|
提供 iterator 的对象方法 | Symbol.iterator | Symbol.asyncIterator | |
next() 返回的值是 | 任意值 | Promise | |
要进行循环,使用 | for..of | for await..of | |
Spread 语法 ... 无法异步工作:需要常规的同步 iterator 的功能,无法与异步 iterator 一起使用。 |
- 回顾 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
}
- 异步 generator (finally)
- 对于大多数的实际应用程序,当我们想创建一个异步生成一系列值的对象时,我们都可以使用异步 generator。
- 在
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;
}
}
}