1. 概念原理与基本用法
生成器的工作机制
在前端开发中,生成器函数通过 function* 声明,创建一个可暂停与恢复执行的迭代器对象。它遵循 Iterator 协议,第一次调用 next() 时进入到函数体的第一条 yield 语句处,随后返回一个 { value, done } 对象,表示当前产出值以及是否结束。暂停与恢复的能力使得复杂流程可以分段执行,降低一次性计算的压力。
生成器的核心是 yield,它既是产出值的信号,也是执行的暂停点。通过多次 next() 调用,生成器逐步推进,直到遇到最后一个 yield 或者抛出异常为止。此特性常用于实现惰性计算、数据流控流与自定义迭代器。
function* numbers() {yield 1;yield 2;yield 3;
}
const g = numbers();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
当遍历一个生成器时,for...of 会隐式调用 iterator.next(),直到返回 { done: true }。这使得生成器在常规循环场景下也非常易用,且能将复杂异步流程抽象成直观的流水线。
异步迭代的基础
要在异步情景下使用生成器,需要理解 async function* 与异步迭代器的结合。异步生成器通过 yield 暴露 Promise,消费者通过 for await...of 来逐步消费,确保每一步都在上一个异步操作完成后继续。
在语义层面,异步生成器产生的每个值,实际是一个 Promise<IteratorResult>,需要用 await 处理,才能得到实际的 { value, done }。这为从网络请求、动画帧、文件读取等异步来源组织数据流提供了天然的结构。

async function* asyncNumbers() {yield await Promise.resolve(1);yield await Promise.resolve(2);yield await Promise.resolve(3);
}
(async () => {for await (const n of asyncNumbers()) {console.log(n);}
})();
此外,异步迭代还涉及 [Symbol.asyncIterator] 的实现,确保对象可以被 for await...of 安全遍历。对比同步生成器,异步生成器在处理 I/O 与延迟操作时的控制粒度更细。
2. 异步迭代的原理与实现
异步生成器与for await...of
异步生成器的典型写法是 async function*,内部仍可使用 yield,但每次产出都可能是一个 Promise。外部消费端使用 for await...of,在每次迭代前自动等待 Promise 解决,确保顺序一致性。
在实现层面,next() 返回一个 Promise,解析后得到 IteratorResult 对象。若 done 为 true,则循环结束;否则将 value 作为下一轮的输入值继续处理。
async function* fetchPages(urls) {for (const url of urls) {const res = await fetch(url);yield await res.json();}
}
(async () => {for await (const page of fetchPages(['a.json', 'b.json'])) {console.log(page);}
})();
通过上述模式,数据可以逐页拉取、逐条消费,避免一次性拉取导致的内存与网络压力峰值。异步迭代将网络 IO、动画节流、用户输入等事件统一治理为数据流的逐步推进。
实现细节与边界条件
在实际应用中,需要关注错误处理、取消机制与背压控制。try/catch 可以在异步生成器内部捕获错误,外部消费者通过 catch 捕获整条流水线中抛出的异常。同时,throw 允许把错误注入到生成器内部以触发自带的清理逻辑。
边界条件包括:数据源为空、网络请求失败、返回格式不符等情况。为了健壮性,可以在生成器内部对 data 的结构进行校验,并在必要时发出 break 或 return 结束迭代。
async function* safeStream(source) {try {for await (const item of source) {if (item == null) continue;yield item;}} catch (e) {console.error('流处理错误', e);throw e;}
}3. 实战案例一:数据分页加载的生成器实现
需求分析与设计要点
在大数据场景下,分页加载可以显著降低单次请求的压力,并通过 异步迭代实现对每条数据的逐条处理。设计要点包括:按需加载、内存友好、以及对错误与空结果的鲁棒处理。
通过把分页接口抽象为一个异步生成器,可以将网络请求、数据解包和消费逻辑解耦,让流水线更易测试与扩展。
async function* paged(fetchPage, pageSize = 50) {let page = 1;while (true) {const data = await fetchPage(page, pageSize);if (!data.items || data.items.length === 0) break;for (const item of data.items) yield item;page++;}
}
使用示例
通过 for await...of 驱动整个分页数据的消费,数据项可以在获取到时即刻被处理,提升可感知的响应性。
async function main() {for await (const item of paged(fetchPage)) {console.log(item);}
}
其中,fetchPage 是对服务端分页 API 的包装,返回结构中包含 items 数组与分页信息。
async function fetchPage(page, size) {const res = await fetch(`/api/items?page=${page}&size=${size}`);return await res.json();
}
4. 实战案例二:与前端事件循环结合的数据流管线
事件驱动的数据管线
将事件源转化为异步迭代器后,解耦事件产生与处理,使得管线各阶段职责更清晰。生产者将数据推入队列,消费者通过异步迭代逐步消费,降低回调地狱的复杂度。
典型路径是:producer → transform → consumer,其中每一环都可独立实现与测试。
async function* eventStream(emitter) {const queue = [];emitter.on('data', (d) => queue.push(d));while (true) {if (queue.length) yield queue.shift();else await new Promise(r => setTimeout(r, 50));}
}
数据管线的组合
通过将输入通过 transform 变换后再交给 sink 消费,可以实现流式处理的组合式设计。使用 for await...of 驱动整个链路,逻辑更直观且易于维护。
async function* transform(input) {for await (const x of input) {yield x * 2; // 简单映射示例}
}
async function* sink(input) {for await (const x of input) {console.log(x);}
} 

