JavaScript中的异步操作传统上多使用回调函数或者Promise处理,当异步流程层级变多时,很容易出现回调地狱或者Promise链式调用过长的问题。Generator函数与yield关键字的组合,为异步流程管理提供了一种更灵活的实现方式,让异步代码可以拥有同步代码的阅读体验。

Generator函数基础
Generator函数是ES6引入的特殊函数,执行时不会立即执行函数体,而是返回一个迭代器对象,通过迭代器的next方法控制函数体的执行。
定义Generator函数需要在function关键字后面加星号,函数内部可以使用yield关键字暂停执行:
// 定义Generator函数
function* gen() {
console.log('开始执行');
yield '第一个暂停点';
console.log('恢复执行');
yield '第二个暂停点';
console.log('执行结束');
}
// 获取迭代器
const iterator = gen();
// 第一次调用next,执行到第一个yield暂停
console.log(iterator.next()); // 输出:开始执行 { value: '第一个暂停点', done: false }
// 第二次调用next,从上次暂停处继续执行到第二个yield
console.log(iterator.next()); // 输出:恢复执行 { value: '第二个暂停点', done: false }
// 第三次调用next,执行剩余代码
console.log(iterator.next()); // 输出:执行结束 { value: undefined, done: true }
yield关键字的工作机制
yield关键字的作用是暂停Generator函数的执行,同时可以将后面表达式的值作为当前next方法返回对象的value属性值。当再次调用next方法时,函数会从上次暂停的yield位置继续执行。
另外,next方法还可以接收参数,这个参数会作为上一个yield表达式的返回值,这是实现异步流程传递结果的关键:
function* gen() {
const a = yield 1;
console.log('a的值:', a); // 输出:a的值: 2
const b = yield a + 1;
console.log('b的值:', b); // 输出:b的值: 3
}
const iterator = gen();
iterator.next(); // 执行到第一个yield,返回{ value: 1, done: false }
iterator.next(2); // 传入2作为第一个yield的返回值,赋值给a,执行到第二个yield,返回{ value: 3, done: false }
iterator.next(3); // 传入3作为第二个yield的返回值,赋值给b,执行结束,返回{ value: undefined, done: true }
结合Generator管理异步流程
Generator函数本身不能直接处理异步,但是可以通过执行器封装,让yield后面跟随异步操作,当异步操作完成后再继续执行下一步,从而实现异步流程的同步化写法。
基础异步执行器实现
下面我们实现一个简单的执行器,自动执行Generator函数中的异步操作:
// 模拟异步请求函数
function mockAsyncRequest(url) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`请求${url}成功,返回数据`);
}, 1000);
});
}
// 执行器函数,自动执行Generator中的异步操作
function runGenerator(gen) {
const iterator = gen();
function next(data) {
const result = iterator.next(data);
if (result.done) {
return result.value;
}
// 如果yield后面是Promise,等待Promise完成后再继续执行
if (result.value && typeof result.value.then === 'function') {
result.value.then((res) => {
next(res);
});
} else {
next(result.value);
}
}
next();
}
// 使用Generator管理异步流程
function* asyncFlow() {
console.log('开始第一个请求');
const res1 = yield mockAsyncRequest('/api/user');
console.log('第一个请求结果:', res1);
console.log('开始第二个请求');
const res2 = yield mockAsyncRequest('/api/order');
console.log('第二个请求结果:', res2);
console.log('所有异步操作完成');
}
// 执行异步流程
runGenerator(asyncFlow);
// 输出顺序:
// 开始第一个请求
// 1秒后输出:第一个请求结果: 请求/api/user成功,返回数据
// 开始第二个请求
// 再1秒后输出:第二个请求结果: 请求/api/order成功,返回数据
// 所有异步操作完成
处理异步错误的场景
实际开发中异步操作可能失败,我们需要在Generator中捕获错误,这时候可以结合迭代器的throw方法实现:
// 模拟可能失败的异步请求
function mockAsyncRequestWithError(url, shouldFail = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) {
reject(new Error(`请求${url}失败`));
} else {
resolve(`请求${url}成功`);
}
}, 1000);
});
}
// 支持错误处理的执行器
function runGeneratorWithError(gen) {
const iterator = gen();
function next(data) {
let result;
try {
result = iterator.next(data);
} catch (e) {
// 捕获Generator内部抛出的错误
console.log('捕获到错误:', e.message);
return;
}
if (result.done) {
return result.value;
}
if (result.value && typeof result.value.then === 'function') {
result.value.then(
(res) => next(res),
(err) => {
// 异步操作失败时,通过throw方法将错误抛入Generator内部
iterator.throw(err);
}
);
} else {
next(result.value);
}
}
next();
}
// 使用带错误处理的异步流程
function* asyncFlowWithError() {
try {
const res1 = yield mockAsyncRequestWithError('/api/user');
console.log('第一个请求结果:', res1);
// 第二个请求设置为失败
const res2 = yield mockAsyncRequestWithError('/api/order', true);
console.log('第二个请求结果:', res2);
} catch (e) {
console.log('流程中捕获错误:', e.message);
}
}
runGeneratorWithError(asyncFlowWithError);
// 输出顺序:
// 1秒后输出:第一个请求结果: 请求/api/user成功
// 再1秒后输出:流程中捕获错误: 请求/api/order失败
与async/await的对比
ES7引入的async/await本质上就是Generator函数与执行器的语法糖,两者的核心思路一致,但是async/await是语言层面提供的方案,不需要手动编写执行器,使用更简洁:
| 对比项 | Generator+yield | async/await |
|---|---|---|
| 执行方式 | 需要手动编写执行器或者借助co等第三方库 | 语言内置支持,直接执行即可 |
| 返回值 | 返回迭代器对象 | 返回Promise对象 |
| 语法复杂度 | 需要function*、yield关键字,执行器封装较繁琐 | 仅需async、await关键字,语法更简洁 |
| 适用场景 | 需要更细粒度控制执行流程的场景 | 绝大多数常规异步流程管理场景 |
虽然async/await是更推荐的方案,但是理解Generator与yield的异步管理思路,能够帮助我们更深入理解JavaScript异步编程的演进逻辑,在遇到需要自定义执行流程的场景时也能更灵活应对。
注意事项
- Generator函数执行返回的是迭代器,不会立即执行函数体,需要调用
next方法才会开始执行。 yield关键字只能在Generator函数内部使用,在普通函数中会报错。- 如果不需要管理异步流程,仅使用Generator函数做状态机或者惰性求值场景,也可以不使用执行器,直接手动控制
next调用即可。 - 手动封装执行器时需要注意处理Promise的异常,避免异步错误被静默忽略。
Generatoryield异步流程管理JavaScript修改时间:2026-06-14 01:27:47