生成器函数是可暂停执行的函数,用function*声明、yield暂停并产出值,调用后返回迭代器,需手动.next()推进;yield不自动等待Promise,需外部runner配合await;async/await已取代其大部分用途。

生成器函数是什么?function* 和 yield 的真实作用
生成器函数不是异步语法糖,它本质是**可暂停执行的函数**,靠 function* 声明、用 yield 暂停并产出值。调用后返回一个迭代器对象,必须手动调用 .next() 才会继续执行到下一个 yield 或函数结束。
它不自动处理异步,但提供了控制流“断点”的能力——这正是配合异步操作的关键基础。
-
yield后面的表达式会立即求值,但函数执行权交还给调用方,后续需再次.next()才继续 - 生成器内部的
return会令迭代器的done: true,且value为返回值 - 不能用
async function*直接让生成器变成“异步生成器”,那是另一套机制(AsyncIterator)
如何用 yield 暂停并等待 Promise 完成?
直接 yield fetch('/api') 不会等请求完成,只是把 Promise 对象当普通值产出。真正要等它,得靠外部协程驱动器(co 函数或手写 runner)来捕获 yield 出的 Promise 并 await 它。
下面是一个最小可行 runner 示例:
立即学习“Java免费学习笔记(深入)”;
function run(genFn) {
const gen = genFn();
function next(data) {
const { value, done } = gen.next(data);
if (done) return value;
return Promise.resolve(value).then(next);
}
return next();
}使用时:
function* apiFlow() {
const res = yield fetch('/user');
const user = yield res.json();
yield console.log(user.name);
}
run(apiFlow); // 自动 await 每个 yield 出的 Promise- runner 必须递归调用
.next(),且对每个value做Promise.resolve().then() - 如果
yield出的不是 Promise,Promise.resolve()会原样包裹,不影响流程 - 错误需在 runner 内用
gen.throw()传递,否则会被静默吞掉
async/await 普及后,还有必要手写生成器驱动吗?
基本没必要。现代 JavaScript 中 async/await 已覆盖全部常见异步场景,语法更直白,调试更友好,V8 引擎优化也更充分。
生成器驱动方案只在极少数场景仍有价值:
- 需要精确控制每一步暂停/恢复时机(如实现自定义状态机、协程调度)
- 兼容非常老的运行时(IE11 或无
async支持的嵌入 JS 引擎),且无法引入 Babel 转译 - 已有基于
co的旧代码库,迁移成本过高
注意:async function* 是“异步生成器函数”,返回 AsyncIterator,和用 yield 驱动 Promise 完全不同——它用于 for await...of 流式消费异步数据源(如 SSE、数据库游标),不是替代 async/await 的方式。
容易被忽略的坑:生成器的状态不可重用
生成器实例(iterator)是一次性的。一旦执行到 done: true,再调用 .next() 永远返回 { value: undefined, done: true };也不会自动重置。
- 每次需要新流程,必须重新调用生成器函数(
genFn())创建新迭代器 - 闭包变量不会重置,但生成器函数体内的局部变量每次调用都是全新的
- 不要试图把生成器当作类方法反复调用同一个实例——这是最常见的误用
异步流程中若需多次触发同一逻辑,应封装为函数返回新生成器,而非复用旧 iterator。











