异步迭代器(for-await-of)
ECMAScript 9 引入了异步迭代器(Async Iterators)和 for-await-of
循环,为处理异步数据流提供了更直观的语法。这一特性使得开发者能够以同步的方式编写异步代码,尤其在处理分页API、流式数据或任何需要逐步解析的异步操作时非常有用。
异步迭代器的基础概念
异步迭代器是普通迭代器的异步版本,它通过 Symbol.asyncIterator
方法返回一个异步迭代器对象。异步迭代器的 next()
方法返回一个 Promise,该 Promise 解析为一个包含 value
和 done
属性的对象。
const asyncIterable = {
[Symbol.asyncIterator]() {
let i = 0;
return {
next() {
if (i < 3) {
return Promise.resolve({ value: i++, done: false });
}
return Promise.resolve({ done: true });
}
};
}
};
for-await-of 循环
for-await-of
是专门为异步迭代器设计的循环语法,它会自动等待每个 Promise 解析后再继续下一次迭代。以下是一个简单的示例:
async function processAsyncData() {
for await (const item of asyncIterable) {
console.log(item); // 依次输出 0, 1, 2
}
}
processAsyncData();
实际应用场景
分页API的数据获取
假设有一个分页API,每次请求返回一页数据。使用异步迭代器可以轻松实现逐页获取数据的功能:
async function* fetchPaginatedData(url) {
let page = 1;
while (true) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
if (data.length === 0) break;
yield data;
page++;
}
}
(async () => {
for await (const pageData of fetchPaginatedData('https://api.example.com/items')) {
console.log('Received page:', pageData);
}
})();
流式数据处理
Node.js 中的 ReadableStream
也支持异步迭代,可以方便地处理大文件或网络流:
import { createReadStream } from 'fs';
async function processFile(filePath) {
const stream = createReadStream(filePath, { encoding: 'utf8' });
for await (const chunk of stream) {
console.log('Received chunk:', chunk);
}
}
processFile('./large-file.txt');
错误处理
在异步迭代过程中,错误可以通过 try-catch
捕获:
async function* generateWithError() {
yield 1;
throw new Error('Something went wrong');
yield 2; // 不会执行
}
(async () => {
try {
for await (const num of generateWithError()) {
console.log(num);
}
} catch (err) {
console.error('Caught error:', err.message);
}
})();
与同步迭代器的区别
- 异步迭代器使用
Symbol.asyncIterator
而非Symbol.iterator
next()
方法返回 Promise- 必须使用
for-await-of
而非for-of
循环 - 异步迭代器可以在每次迭代中执行异步操作
实现自定义异步可迭代对象
以下是一个模拟异步数据源的完整示例:
class AsyncDataSource {
constructor(data, delay) {
this.data = data;
this.delay = delay;
}
async *[Symbol.asyncIterator]() {
for (const item of this.data) {
await new Promise(resolve => setTimeout(resolve, this.delay));
yield item;
}
}
}
(async () => {
const asyncData = new AsyncDataSource(['a', 'b', 'c'], 1000);
for await (const letter of asyncData) {
console.log(letter); // 每隔1秒输出一个字母
}
})();
性能考虑
使用异步迭代器时需要注意:
- 顺序执行特性可能导致性能瓶颈
- 大量小规模异步操作可能产生额外开销
- 在某些场景下,并行处理可能更高效
浏览器和Node.js支持
现代浏览器和Node.js(v10+)都已支持异步迭代器。在不支持的环境中,可以通过Babel等工具进行转译。可以通过以下代码检测支持情况:
const isAsyncIterable = (obj) =>
obj != null && typeof obj[Symbol.asyncIterator] === 'function';
console.log('Support:', isAsyncIterable({ [Symbol.asyncIterator]() {} }));
与其他异步模式的比较
与Promise.all和async/await相比,异步迭代器更适合:
- 数据量未知或可能无限的情况
- 需要逐步处理数据的场景
- 内存受限的环境(因为不需要一次性加载所有数据)
// 传统方式处理分页
async function getAllPages(url) {
let allData = [];
let page = 1;
while (true) {
const res = await fetch(`${url}?page=${page}`);
const data = await res.json();
if (data.length === 0) break;
allData = allData.concat(data);
page++;
}
return allData;
}
// 使用异步迭代器更节省内存
async function processPages(url) {
for await (const page of fetchPaginatedData(url)) {
// 逐页处理,不保存所有数据
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn