生成器的工作原理与核心概念
什么是 ES6 生成器
在前端开发中,生成器函数是通过 function* 声明的函数,执行时会返回一个可迭代的生成器对象。调用 next() 时,代码执行到遇到 yield 的点就会暂停,返回一个值给外部调用者。这个暂停-再进入的机制,是实现异步控制的一种自然方式。
与普通函数相比,生成器拥有一个“暂停点”的执行流,允许在异步操作之间保持局部变量状态。通过这种方式,我们能够把连续的异步任务以代码顺序的形式表达,而不是深层回调。
生成器的执行模型与暂停点
生成器在调用 生成器对象的 next() 时,会从上一次暂停的位置继续执行,直到遇到下一个 yield 或 return。这就形成了一个简单的“请求-响应”循环:外部通过 yield 暴露异步请求,生成器在下一次迭代中接收返回值。
重要的是理解 返回值与当前位置的关系:yield 后的表达式会成为 next() 的参数,以此将结果带回生成器内部。这样我们就能在同步语义中表达异步依赖。
function* gen() {
const a = yield Promise.resolve(1);
const b = yield Promise.resolve(a + 1);
return b;
}
用生成器实现异步控制的模式
手写轮子:生成器+Promise 的协作
要让生成器真正驱动异步流程,通常需要一个驱动循环(runner)来处理 Promise 的解析与传递。手写轮子可以帮助理解底层机制,同时也作为学习工具。
典型思路是:创建一个递归的 handle 函数,通过 generator.next() 获取 value,若 value 是 Promise,则等待其完成再将结果作为参数传回生成器;若是最终结果或异常,则结束或抛出。
function run(gen) {
const iterator = gen();
function handle(result) {
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value).then(
res => handle(iterator.next(res)),
err => handle(iterator.throw(err))
);
}
try {
return handle(iterator.next());
} catch (e) {
return Promise.reject(e);
}
}
// 使用示例
run(function* () {
const data1 = yield fetch('/api/a').then(r => r.json());
const data2 = yield fetch('/api/b').then(r => r.json());
return [data1, data2];
}).then(console.log).catch(console.error);
错误处理与并发控制
对于并发场景,生成器并不能天然实现并行,需要通过 并发策略(例如并发上限、并行分发)或回退机制来控制。通过在 yield 处注入错误处理,可以实现一个简单的恢复策略。
在实际代码中,常见做法是为每一个 yield 提供一个统一的错误处理分支,确保异常不会直接崩溃整个流程。使用 try/catch 和 generator.throw() 可以将错误重新抛回生成器,视作异步步骤的失败信号。
实战技巧:在前端项目中应用
串行化远程请求的模式
许多前端场景需要串行化执行异步请求,例如依赖前一个请求的结果。使用生成器,我们可以把这些请求写成顺序的、可读性高的代码风格。yield 允许你将每一步的 Promise 暴露给驱动器,保持程序的线性结构。
下面的示例展示了如何用生成器实现一个串行的数据获取流程:先获取用户信息,再根据用户信息获取其权限数据。
function* loadUserAndPermissions(userId) {
const user = yield fetch(`/api/users/${userId}`).then(r => r.json());
const perms = yield fetch(`/api/users/${userId}/permissions`).then(r => r.json());
return { user, perms };
}
结合 Promise 的集成模式
为了与现有的 Promise API 保持兼容,可以把生成器与一个轻量的调度器结合,使用 yield 将 Promise 暴露给调度器,调度器再将结果重新注入到生成器。这样可以在不引入大型库的情况下实现异步控制的整洁结构。
// 简化调度器示例
function runGen(gen) {
const it = gen();
function next(p) {
const { value, done } = it.next(p);
if (done) return Promise.resolve(value);
return Promise.resolve(value).then(next);
}
return next();
}
runGen(function* () {
const a = yield Promise.resolve(3);
const b = yield Promise.resolve(a + 4);
return b;
}).then(console.log);
注意事项与性能考量
浏览器兼容性与 polyfill
在 ES6 时代,生成器已得到广泛支持,但在某些旧环境中需要 polyfill 或降级方案。对现代浏览器,yield 与 async/await 的关系需要理解:生成器是底层实现的原型,Async/Await 则提供了更直观的语法糖。
如果你在项目中目标是最大兼容性,可以先评估是否真的需要生成器驱动的异步控制,或直接采用 async/await,结合 Promises 实现同样的结果,使代码更易维护。
与 async/await 的关系
从语义层面看,async/await 是对基于生成器的协程模式的一个提升,它把 yield 替换为 await,并自动处理 Promise 的解析。理解生成器的机制有助于更好地理解 async/await 的工作原理。


