JavaScript的秘密生活:Async Generator
Source: Dev.to
如何使用 for await...of 处理数据流。
Timothy 正在揉搓他的太阳穴。屏幕上是一段看起来像在进行一场无望战斗的函数。
async function getAllUsers() {
let url = '/api/users?page=1';
const allUsers = [];
while (url) {
const response = await fetch(url);
const data = await response.json();
// Add this page's users to our big list
allUsers.push(...data.users);
// Prepare for the next loop... if there is one
url = data.nextPage;
}
return allUsers;
}
“我想把所有用户数据都下载下来,”Timothy 向 Margaret 解释道。“可是有 50,000 条用户记录。如果我等所有页面都下载完才开始处理,用户要等 20 秒。感觉……卡住了。”
Margaret 点点头。“你把 Stream 当成 Bucket 来使用,”她说。
“你想把每一滴水都收集起来才让人喝,”她继续道。“为什么不直接让他们从水管里喝呢?”
混合体
Margaret 在白板上写下了一种新语法。它结合了语言中最强大的两个关键字。
async function* fetchUsers() { ... }
Async 与 Generator 相遇。async 让我们可以等待网络请求,* 让我们一次产出一块数据。
她重写了 Timothy 的代码,并加入了安全网。
async function* fetchUsers() {
let url = '/api/users?page=1';
while (url) {
try {
const response = await fetch(url);
const data = await response.json();
// Instead of building a massive array, we deliver this page immediately
for (const user of data.users) {
yield user;
}
url = data.nextPage;
} catch (error) {
console.error("Stream interrupted", error);
return; // Stop the stream safely
}
}
}
魔法循环(for await...of)
“这就是魔法发生的地方,”Margaret 说。“我们需要一个会等待的循环。”
她写下了消费代码:
const userStream = fetchUsers();
for await (const user of userStream) {
console.log("Processing:", user.name);
// This loop automatically PAUSES while the next page downloads!
}
Timothy 观察着控制台的模拟。
- 循环立即打印出 10 位用户。
- 循环 暂停(网络正在获取第 2 页)。
- 循环恢复并再打印出 10 位用户。
“暂停是看不见的,”Timothy 低声说。
“正是如此,”Margaret 回答。“循环内部的代码并不知道它在等待。它只是在请求下一个用户,而 JavaScript 负责处理暂停。你不是在处理 Memory Snapshot,而是在处理 Time。”
紧急刹车
“还有一点,”Margaret 降低声音补充道。“在真实世界里,流可能是无止境的。有时用户会在你完成之前离开页面。”
“我该怎么办?”
“使用 AbortController,”她说。“它可以让你切断水管。一定要让你的流能够被停止。”
结论
Timothy 删除了 allUsers 数组。他不再需要那个桶了。
“感觉轻松多了,”Timothy 说。“我不再囤积数据。”
“这就是 Async Generator 的禅意,”Margaret 微笑道。“不要背负未来的重量,只处理眼前的内容。”