程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • ES6

    • ECMAScript 6 简介
    • let 和 const 命令
    • 变量的解构赋值
    • 字符串的扩展
    • 字符串的新增方法
    • 正则的扩展
    • 数值的扩展
    • 函数的扩展
    • 数组的扩展
    • 对象的扩展
    • 对象的新增方法
    • Symbol
    • Set 和 Map 数据结构
    • Proxy
    • Reflect
    • Promise 对象
    • Iterator 和 for-of 循环
    • Generator 函数的语法
    • Generator 函数的异步应用
    • async 函数
    • Class 的基本语法
    • Class 的继承
    • Module 的语法
    • Module 的加载实现
    • 编程风格
    • 读懂 ECMAScript 规格
    • 异步遍历器
      • 1. 同步遍历器的问题
      • 2. 异步遍历的接口
      • 3. for await...of
      • 4. 异步 Generator 函数
      • 5. yield* 语句
        • 示例代码
        • 详细示例
    • ArrayBuffer
    • 最新提案
    • 装饰器
    • 函数式编程
    • Mixin
    • SIMD
    • 参考链接
  • ES6
  • ES6
scholar
2024-07-26
目录

异步遍历器

# 异步遍历器

# 1. 同步遍历器的问题

《遍历器》一章说过,Iterator 接口是一种数据遍历的协议,只要调用遍历器对象的 next 方法,就会得到一个对象,表示当前遍历指针所在的那个位置的信息。next 方法返回的对象的结构是 {value, done},其中 value 表示当前的数据的值,done 是一个布尔值,表示遍历是否结束。

function idMaker() {
  let index = 0;

  return {
    next: function() {
      return { value: index++, done: false }; // 返回包含 value 和 done 的对象
    }
  };
}

const it = idMaker();

console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2
// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

上面代码中,变量 it 是一个遍历器(iterator)。每次调用 it.next() 方法,就返回一个对象,表示当前遍历位置的信息。

这里隐含着一个规定,it.next() 方法必须是同步的,只要调用就必须立刻返回值。也就是说,一旦执行 it.next() 方法,就必须同步地得到 value 和 done 这两个属性。如果遍历指针正好指向同步操作,当然没有问题,但对于异步操作,就不太合适了。

function idMaker() {
  let index = 0;

  return {
    next: function() {
      return new Promise(function (resolve, reject) { // 返回 Promise 对象
        setTimeout(() => {
          resolve({ value: index++, done: false });
        }, 1000); // 模拟异步操作,1秒后返回结果
      });
    }
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13

上面代码中,next() 方法返回的是一个 Promise 对象,这样就不行,不符合 Iterator 协议,只要代码里面包含异步操作都不行。也就是说,Iterator 协议里面 next() 方法只能包含同步操作。

目前的解决方法是,将异步操作包装成 Thunk 函数或者 Promise 对象,即 next() 方法返回值的 value 属性是一个 Thunk 函数或者 Promise 对象,等待以后返回真正的值,而 done 属性则还是同步产生的。

function idMaker() {
  let index = 0;

  return {
    next: function() {
      return {
        value: new Promise(resolve => setTimeout(() => resolve(index++), 1000)), // 返回 Promise 对象
        done: false
      };
    }
  };
}

const it = idMaker();

it.next().value.then(o => console.log(o)); // 0
it.next().value.then(o => console.log(o)); // 1
it.next().value.then(o => console.log(o)); // 2
// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

上面代码中,value 属性的返回值是一个 Promise 对象,用来放置异步操作。但是这样写很麻烦,不太符合直觉,语义也比较绕。

ES2018 引入 (opens new window) 了“异步遍历器”(Async Iterator),为异步操作提供原生的遍历器接口,即 value 和 done 这两个属性都是异步产生。

# 2. 异步遍历的接口

异步遍历器的最大的语法特点,就是调用遍历器的 next 方法,返回的是一个 Promise 对象。

asyncIterator
  .next()
  .then(
    ({ value, done }) => /* 处理逻辑 */
  );
1
2
3
4
5

上面代码中,asyncIterator 是一个异步遍历器,调用 next 方法以后,返回一个 Promise 对象。因此,可以使用 then 方法指定,这个 Promise 对象的状态变为 resolve 以后的回调函数。回调函数的参数,则是一个具有 value 和 done 两个属性的对象,这个跟同步遍历器是一样的。

我们知道,一个对象的同步遍历器的接口,部署在 Symbol.iterator 属性上面。同样地,对象的异步遍历器接口,部署在 Symbol.asyncIterator 属性上面。不管是什么样的对象,只要它的 Symbol.asyncIterator 属性有值,就表示应该对它进行异步遍历。

下面是一个异步遍历器的例子。

// 创建一个异步可迭代对象
function createAsyncIterable(array) {
  return {
    [Symbol.asyncIterator]() {
      let i = 0;
      return {
        next() {
          if (i < array.length) {
            return new Promise(resolve => {
              setTimeout(() => resolve({ value: array[i++], done: false }), 1000); // 模拟异步操作,1秒后返回结果
            });
          }
          return Promise.resolve({ value: undefined, done: true }); // 遍历结束
        }
      };
    }
  };
}

const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();

asyncIterator
.next()
.then(iterResult1 => {
  console.log(iterResult1); // { value: 'a', done: false }
  return asyncIterator.next();
})
.then(iterResult2 => {
  console.log(iterResult2); // { value: 'b', done: false }
  return asyncIterator.next();
})
.then(iterResult3 => {
  console.log(iterResult3); // { value: undefined, done: true }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

上面代码中,异步遍历器其实返回了两次值。第一次调用的时候,返回一个 Promise 对象;等到 Promise 对象 resolve 了,再返回一个表示当前数据成员信息的对象。这就是说,异步遍历器与同步遍历器最终行为是一致的,只是会先返回 Promise 对象,作为中介。

由于异步遍历器的 next 方法,返回的是一个 Promise 对象。因此,可以把它放在 await 命令后面。

async function f() {
  const asyncIterable = createAsyncIterable(['a', 'b']);
  const asyncIterator = asyncIterable[Symbol.asyncIterator]();
  console.log(await asyncIterator.next());
  // { value: 'a', done: false }
  console.log(await asyncIterator.next());
  // { value: 'b', done: false }
  console.log(await asyncIterator.next());
  // { value: undefined, done: true }
}
1
2
3
4
5
6
7
8
9
10

上面代码中,next 方法用 await 处理以后,就不必使用 then 方法了。整个流程已经很接近同步处理了。

注意,异步遍历器的 next 方法是可以连续调用的,不必等到上一步产生的 Promise 对象 resolve 以后再调用。这种情况下,next 方法会累积起来,自动按照每一步的顺序运行下去。下面是一个例子,把所有的 next 方法放在 Promise.all 方法里面。

const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
const [{value: v1}, {value: v2}] = await Promise.all([
  asyncIterator.next(), asyncIterator.next()
]);

console.log(v1, v2); // a b
1
2
3
4
5
6
7

另一种用法是一次性调用所有的 next 方法,然后 await 最后一步操作。

async function runner() {
  const writer = openFile('someFile.txt'); // 假设 openFile 是一个异步函数,返回一个异步遍历器
  writer.next('hello'); // 写入 'hello'
  writer.next('world'); // 写入 'world'
  await writer.return(); // 关闭文件
}

runner();
1
2
3
4
5
6
7
8

# 3. for await...of

前面介绍过,for...of循环用于遍历同步的 Iterator 接口。新引入的for await...of循环,则是用于遍历异步的 Iterator 接口。

async function f() {
  for await (const x of createAsyncIterable(['a', 'b'])) {
    console.log(x); // 输出每个异步迭代的值
  }
}
// 输出:
// a
// b
1
2
3
4
5
6
7
8

上面代码中,createAsyncIterable()返回一个拥有异步遍历器接口的对象,for...of循环自动调用这个对象的异步遍历器的next方法,会得到一个 Promise 对象。await用来处理这个 Promise 对象,一旦resolve,就把得到的值(x)传入for...of的循环体。

for await...of循环的一个用途,是部署了 asyncIterable 操作的异步接口,可以直接放入这个循环。

let body = '';

async function f() {
  for await(const data of req) body += data; // 异步读取数据并拼接到 body 中
  const parsed = JSON.parse(body); // 解析 JSON 数据
  console.log('got', parsed); // 输出解析后的数据
}
1
2
3
4
5
6
7

上面代码中,req是一个 asyncIterable 对象,用来异步读取数据。可以看到,使用for await...of循环以后,代码会非常简洁。

如果next方法返回的 Promise 对象被reject,for await...of就会报错,要用try...catch捕捉。

async function f() {
  try {
    for await (const x of createRejectingIterable()) {
      console.log(x); // 尝试输出每个异步迭代的值
    }
  } catch (e) {
    console.error(e); // 捕捉并输出错误信息
  }
}
1
2
3
4
5
6
7
8
9

注意,for await...of循环也可以用于同步遍历器。

(async function() {
  for await (const x of ['a', 'b']) {
    console.log(x); // 输出每个同步迭代的值
  }
})();
// 输出:
// a
// b
1
2
3
4
5
6
7
8

Node v10 支持异步遍历器,Stream 就部署了这个接口。下面是读取文件的传统写法与异步遍历器写法的差异。

const fs = require('fs');

// 传统写法
function main(inputFilePath) {
  const readStream = fs.createReadStream(
    inputFilePath,
    { encoding: 'utf8', highWaterMark: 1024 } // 每次读取 1024 字节
  );
  readStream.on('data', (chunk) => {
    console.log('>>> ' + chunk); // 输出每个读取到的块
  });
  readStream.on('end', () => {
    console.log('### DONE ###'); // 输出读取结束
  });
}

// 异步遍历器写法
async function main(inputFilePath) {
  const readStream = fs.createReadStream(
    inputFilePath,
    { encoding: 'utf8', highWaterMark: 1024 } // 每次读取 1024 字节
  );

  for await (const chunk of readStream) {
    console.log('>>> ' + chunk); // 输出每个读取到的块
  }
  console.log('### DONE ###'); // 输出读取结束
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 4. 异步 Generator 函数

就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。

在语法上,异步 Generator 函数就是async函数与 Generator 函数的结合。

// 定义一个异步 Generator 函数
async function* gen() {
  yield 'hello'; // 产出一个值
}

// 执行异步 Generator 函数,返回一个异步遍历器对象
const genObj = gen();

// 调用异步遍历器对象的 next 方法,返回一个 Promise 对象
genObj.next().then(x => console.log(x));
// 输出:
// { value: 'hello', done: false }
1
2
3
4
5
6
7
8
9
10
11
12

上面代码中,gen是一个异步 Generator 函数,执行后返回一个异步 Iterator 对象。对该对象调用next方法,返回一个 Promise 对象。

异步遍历器的设计目的之一,就是 Generator 函数处理同步操作和异步操作时,能够使用同一套接口。

// 同步 Generator 函数
function* map(iterable, func) {
  const iter = iterable[Symbol.iterator]();
  while (true) {
    const { value, done } = iter.next(); // 从同步遍历器中获取下一个值
    if (done) break;
    yield func(value); // 对值进行处理并产出
  }
}

// 异步 Generator 函数
async function* map(iterable, func) {
  const iter = iterable[Symbol.asyncIterator]();
  while (true) {
    const { value, done } = await iter.next(); // 从异步遍历器中获取下一个值
    if (done) break;
    yield func(value); // 对值进行处理并产出
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

上面代码中,map是一个 Generator 函数,第一个参数是可遍历对象iterable,第二个参数是一个回调函数func。map的作用是将iterable每一步返回的值,使用func进行处理。上面有两个版本的map,前一个处理同步遍历器,后一个处理异步遍历器,可以看到两个版本的写法基本上是一致的。

下面是另一个异步 Generator 函数的例子。

// 定义一个异步 Generator 函数,读取文件的每一行
async function* readLines(path) {
  let file = await fileOpen(path); // 异步打开文件

  try {
    while (!file.EOF) { // 如果文件未结束
      yield await file.readLine(); // 异步读取文件的一行并产出
    }
  } finally {
    await file.close(); // 关闭文件
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

上面代码中,异步操作前面使用await关键字标明,即await后面的操作,应该返回 Promise 对象。凡是使用yield关键字的地方,就是next方法停下来的地方,它后面的表达式的值(即await file.readLine()的值),会作为next()返回对象的value属性,这一点是与同步 Generator 函数一致的。

异步 Generator 函数内部,能够同时使用await和yield命令。可以这样理解,await命令用于将外部操作产生的值输入函数内部,yield命令用于将函数内部的值输出。

上面代码定义的异步 Generator 函数的用法如下。

// 使用 for await...of 循环遍历异步生成器函数返回的异步遍历器
(async function () {
  for await (const line of readLines(filePath)) {
    console.log(line); // 输出每一行的内容
  }
})();
1
2
3
4
5
6

异步 Generator 函数可以与for await...of循环结合起来使用。

// 定义一个异步 Generator 函数,给每一行添加前缀
async function* prefixLines(asyncIterable) {
  for await (const line of asyncIterable) {
    yield '> ' + line; // 给每一行添加前缀并产出
  }
}
1
2
3
4
5
6

异步 Generator 函数的返回值是一个异步 Iterator,即每次调用它的next方法,会返回一个 Promise 对象,也就是说,跟在yield命令后面的,应该是一个 Promise 对象。如果像上面那个例子那样,yield命令后面是一个字符串,会被自动包装成一个 Promise 对象。

// 定义一个异步函数,获取随机数
function fetchRandom() {
  const url = 'https://www.random.org/decimal-fractions/?num=1&dec=10&col=1&format=plain&rnd=new';
  return fetch(url); // 返回一个 Promise 对象
}

// 定义一个异步 Generator 函数
async function* asyncGenerator() {
  console.log('Start');
  const result = await fetchRandom(); // 异步获取随机数
  yield 'Result: ' + await result.text(); // 异步获取随机数的文本并产出
  console.log('Done');
}

// 执行异步 Generator 函数
const ag = asyncGenerator();
ag.next().then(({ value, done }) => {
  console.log(value); // 输出: Result: 随机数值
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

上面代码中,ag是asyncGenerator函数返回的异步遍历器对象。调用ag.next()以后,上面代码的执行顺序如下。

  1. ag.next()立刻返回一个 Promise 对象。
  2. asyncGenerator函数开始执行,打印出Start。
  3. await命令返回一个 Promise 对象,asyncGenerator函数停在这里。
  4. A 处变成 fulfilled 状态,产生的值放入result变量,asyncGenerator函数继续往下执行。
  5. 函数在 B 处的yield暂停执行,一旦yield命令取到值,ag.next()返回的那个 Promise 对象变成 fulfilled 状态。
  6. ag.next()后面的then方法指定的回调函数开始执行。该回调函数的参数是一个对象{value, done},其中value的值是yield命令后面的那个表达式的值,done的值是false。

A 和 B 两行的作用类似于下面的代码。

// 返回一个新的 Promise 对象
return new Promise((resolve, reject) => {
  fetchRandom()
    .then(result => result.text())
    .then(result => {
      resolve({
        value: 'Result: ' + result, // 生成的值
        done: false, // 迭代未结束
      });
    });
});
1
2
3
4
5
6
7
8
9
10
11

如果异步 Generator 函数抛出错误,会导致 Promise 对象的状态变为reject,然后抛出的错误被catch方法捕获。

// 定义一个异步 Generator 函数
async function* asyncGenerator() {
  throw new Error('Problem!'); // 抛出错误
}

// 执行异步 Generator 函数,并捕获错误
asyncGenerator()
  .next()
  .catch(err => console.log(err)); // 输出: Error: Problem!
1
2
3
4
5
6
7
8
9

注意,普通的 async 函数返回的是一个 Promise 对象,而异步 Generator 函数返回的是一个异步 Iterator 对象。可以这样理解,async 函数和异步 Generator 函数,是封装异步操作的两种方法,都用来达到同一种目的。区别在于,前者自带执行器,后者通过for await...of执行,或者自己编写执行器。下面就是一个异步 Generator 函数的执行器。

// 定义一个异步函数,自动执行异步 Generator 函数
async function takeAsync(asyncIterable, count = Infinity) {
  const result = []; // 存储结果
  const iterator = asyncIterable[Symbol.asyncIterator](); // 获取异步遍历器对象
  while (result.length < count) { // 循环获取值,直到达到指定数量
    const { value, done } = await iterator.next();
    if (done) break; // 如果遍历结束,退出循环
    result.push(value); // 将值添加到结果数组中
  }
  return result; // 返回结果数组
}
1
2
3
4
5
6
7
8
9
10
11

上面代码中,异步 Generator 函数产生的异步遍历器,会通过while循环自动执行,每当await iterator.next()完成,就会进入下一轮循环。一旦done属性变为true,就会跳出循环,异步遍历器执行结束。

下面是这个自动执行器的一个使用实例。

// 定义一个异步函数
async function f() {
  // 定义一个异步 Generator 函数
  async function* gen() {
    yield 'a'; // 产出 'a'
    yield 'b'; // 产出 'b'
    yield 'c'; // 产出 'c'
  }

  return await takeAsync(gen()); // 自动执行异步 Generator 函

数,并返回结果
}

// 执行异步函数
f().then(function (result) {
  console.log(result); // 输出: ['a', 'b', 'c']
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

异步 Generator 函数出现以后,JavaScript 就有了四种函数形式:普通函数、async 函数、Generator 函数和异步 Generator 函数。请注意区分每种函数的不同之处。基本上,如果是一系列按照顺序执行的异步操作(比如读取文件,然后写入新内容,再存入硬盘),可以使用 async 函数;如果是一系列产生相同数据结构的异步操作(比如一行一行读取文件),可以使用异步 Generator 函数。

异步 Generator 函数也可以通过next方法的参数,接收外部传入的数据。

// 定义一个异步 Generator 函数,打开文件并写入内容
const writer = openFile('someFile.txt');
writer.next('hello'); // 立即执行,写入 'hello'
writer.next('world'); // 立即执行,写入 'world'
await writer.return(); // 等待写入结束并关闭文件
1
2
3
4
5

上面代码中,openFile是一个异步 Generator 函数。next方法的参数,向该函数内部的操作传入数据。每次next方法都是同步执行的,最后的await命令用于等待整个写入操作结束。

最后,同步的数据结构,也可以使用异步 Generator 函数。

// 定义一个异步 Generator 函数,将同步数据结构转换为异步数据结构
async function* createAsyncIterable(syncIterable) {
  for (const elem of syncIterable) {
    yield elem; // 产出每个元素
  }
}
1
2
3
4
5
6

上面代码中,由于没有异步操作,所以也就没有使用await关键字。

# 5. yield* 语句

yield* 语句也可以跟一个异步遍历器,用于在一个 Generator 函数里面,执行另一个 Generator 函数。

# 示例代码

// 定义一个异步 Generator 函数 gen1
async function* gen1() {
  yield 'a'; // 产出 'a'
  yield 'b'; // 产出 'b'
  return 2;  // 返回 2
}

// 定义另一个异步 Generator 函数 gen2
async function* gen2() {
  // 使用 yield* 调用 gen1,result 最终会等于 2
  const result = yield* gen1();
  console.log(result); // 打印 result
}

// 执行 gen2 并遍历其结果
(async function () {
  for await (const x of gen2()) {
    console.log(x); // 打印每个值
  }
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

上面代码中,gen2 函数里面的 result 变量,最后的值是 2。这是因为 yield* 表达式会委托给 gen1,将 gen1 的所有值逐个产出,直到 gen1 返回的值最终赋给 result。

与同步 Generator 函数一样,for await...of 循环会展开 yield*,即遍历 gen2 的时候,会自动遍历 gen1 的每个产出的值。

# 详细示例

// 定义一个异步 Generator 函数,生成三个值
async function* gen1() {
  yield 'x'; // 产出 'x'
  yield 'y'; // 产出 'y'
  yield 'z'; // 产出 'z'
  return 3;  // 返回 3
}

// 定义另一个异步 Generator 函数,使用 yield* 调用 gen1
async function* gen2() {
  // 使用 yield* 调用 gen1,接收 gen1 的返回值
  const result = yield* gen1();
  console.log('gen1 result:', result); // 打印 gen1 的返回值
}

// 使用 for await...of 循环遍历 gen2 的结果
(async function () {
  for await (const value of gen2()) {
    console.log(value); // 打印每个值
  }
  // 打印: 
  // x
  // y
  // z
  // gen1 result: 3
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

在上面这个示例中,gen1 是一个异步 Generator 函数,产出三个值 x、y 和 z,最后返回 3。gen2 使用 yield* 调用 gen1,然后接收 gen1 的返回值。for await...of 循环会遍历 gen2 的所有产出的值,并打印它们。

编辑此页 (opens new window)
上次更新: 2024/12/28, 18:32:08
读懂 ECMAScript 规格
ArrayBuffer

← 读懂 ECMAScript 规格 ArrayBuffer→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式