Generator 是 ES6 引入的一种特殊函数,它最大的特点是可以暂停执行,然后恢复执行。普通函数被调用后会一口气从头跑到尾,而 Generator 函数可以在中间某个点“刹车”,把控制权交还给调用者,等调用者“踩油门”后再继续往下跑。这一特性为异步编程、惰性计算、自定义迭代器等都提供了全新的可能。
基础语法与执行机制
Generator 函数的定义形式是 function*,内部使用 yield 关键字声明暂停点。
function* myGenerator() {
console.log('第一步');
const a = yield 1;
console.log('第二步,收到的值是:', a);
const b = yield 2;
console.log('第三步,收到的值是:', b);
return '结束';
}
调用 myGenerator() 并不会立即执行函数体,而是返回一个 迭代器对象(Generator 对象)。这个迭代器实现了迭代协议,拥有 next() 方法。每次调用 next(),函数从当前暂停点执行到下一个 yield 或 return,然后再次暂停。
const gen = myGenerator();
gen.next(); // 输出:第一步,返回 { value: 1, done: false }
gen.next(100); // 输出:第二步,收到的值是: 100,返回 { value: 2, done: false }
gen.next(200); // 输出:第三步,收到的值是: 200,返回 { value: '结束', done: true }
gen.next(); // 返回 { value: undefined, done: true }
关键机制:
yield后的值会被包装成{ value, done }返回给调用者。- 调用者可以通过
next(参数)向 Generator 内部注入数据,这个参数将作为上一个yield表达式的返回值。上例中const a = yield 1;在第二次next(100)时,a才会被赋值为 100,而不是 1。 return语句会使done变为true,值为返回值。- Generator 函数执行完毕后,再调用
next()已经没有意义。
双向通信与执行控制
Generator 不仅可以向外输出数据,还能向内接收数据,实现函数外部和内部的双向通信。这让调用者能够根据外部条件动态干预 Generator 内部的执行逻辑,如同在函数内外架起了一条双向管道。
function* talk() {
const name = yield '你的名字是?';
const age = yield `你好,${name},你的年龄是?`;
yield `${name},你 ${age} 岁,欢迎!`;
}
const chat = talk();
chat.next(); // 启动对话
const resp1 = chat.next('Alice'); // 回答名字
chat.next(25); // 回答年龄
错误处理
Generator 还提供了 throw() 方法,可以向暂停处抛入一个错误。错误可以在 Generator 内部用 try...catch 捕获。
function* errorGen() {
try {
yield 1;
} catch (e) {
console.log('内部捕获:', e);
}
yield 2;
}
const gen = errorGen();
gen.next();
gen.throw(new Error('出错了')); // 内部捕获
gen.next(); // 继续执行
如果内部没有捕获,错误会冒泡到调用处,Generator 也会被终止。
异步应用:配合 Promise 实现同步写法
Generator 的最惊艳应用在于处理异步流程。通过 yield 暂停,结合 Promise,可以将异步代码写得像同步一样,解决回调地狱。
核心思想:写一个自动执行的运行器,接收 Generator 函数,每次调用 next() 获取到 { value },如果是 Promise 就等待它完成,然后把结果传回 Generator 内部;如果不是 Promise,直接传回。如此循环直到完成。
一个简化的自动执行器(co 库的核心原理):
function run(genFn) {
const gen = genFn();
function handle(result) {
if (result.done) return Promise.resolve(result.value);
// 确保 value 是 Promise(也可处理普通值)
return Promise.resolve(result.value).then(
data => handle(gen.next(data)),
err => handle(gen.throw(err))
);
}
try {
return handle(gen.next());
} catch (err) {
return Promise.reject(err);
}
}
使用示例:
function* fetchUser() {
const user = yield fetch('/api/user').then(res => res.json());
const posts = yield fetch(`/api/posts/${user.id}`).then(res => res.json());
return { user, posts };
}
run(fetchUser).then(result => {
console.log('最终结果:', result);
});
这种用同步风格写异步操作的写法,虽然现在大多被 async/await 取代(async/await 本质上是 Generator + 自动执行器的语法糖),但理解 Generator 的异步应用,能帮助你洞悉 async/await 的底层实现。
其他实用场景
- 惰性迭代器:Generator 可以按需生成序列,不一次性占用大量内存,比如生成无限序列、斐波那契数列。
- 状态机:通过
yield驱动状态转移。 - 自定义遍历接口:给普通对象添加
Symbol.iterator方法,返回一个 Generator,让for...of可以遍历该对象。
注意事项与总结
- Generator 一旦迭代器完成(
done: true),就不能再调用next(),否则返回{ value: undefined, done: true }。 yield*可以委托给另一个 Generator 或可迭代对象,递归生成。- 在现代开发中,除非你需要精细控制异步流程或实现复杂的惰性迭代器,否则直接使用
async/await会更简洁。但理解 Generator 是理解异步模型和语言演进的重要一环。