生成器函数
生成器函数
生成器函数是一种特殊的函数,可以暂停执行并在之后恢复。本文将介绍生成器函数的语法、yield关键字的使用以及生成器的常见应用场景。
生成器函数基础
什么是生成器函数
生成器函数是 ES6 引入的一种特殊函数,它可以在执行过程中暂停,稍后再从暂停的地方继续执行。生成器函数在调用时不会立即执行其函数体,而是返回一个生成器对象,这个对象遵循迭代器协议。
语法
生成器函数使用 function*
语法定义(注意星号 *
):
function* generatorFunction() {
yield 1;
yield 2;
yield 3;
}
// 创建一个生成器对象
const generator = generatorFunction();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }
生成器对象
调用生成器函数会返回一个生成器对象,它实现了迭代器协议:
- 有一个
next()
方法,返回{ value, done }
形式的对象 - 实现了
Symbol.iterator
方法,使其成为可迭代对象
function* simple() {
yield 'hello';
yield 'world';
}
const generator = simple();
// 使用 next() 方法
console.log(generator.next()); // { value: 'hello', done: false }
// 生成器对象是可迭代的
for (const value of simple()) {
console.log(value); // 输出: 'hello', 然后是 'world'
}
// 可以使用展开运算符
console.log([...simple()]); // ['hello', 'world']
yield 关键字
基本用法
yield
关键字用于暂停生成器函数的执行,并返回跟在它后面的表达式的值。
function* countUp() {
yield 1;
yield 2;
yield 3;
}
const counter = countUp();
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
console.log(counter.next().value); // 3
yield 表达式
yield
可以返回任何表达式的结果:
function* expressionGenerator() {
yield 1 + 1;
yield Math.random();
yield 'Hello ' + 'World';
}
const gen = expressionGenerator();
console.log(gen.next().value); // 2
console.log(gen.next().value); // 随机数,如 0.123456789
console.log(gen.next().value); // 'Hello World'
yield*(委托生成器)
yield*
表达式用于委托给另一个生成器函数或可迭代对象:
function* gen1() {
yield 1;
yield 2;
}
function* gen2() {
yield 'a';
yield* gen1(); // 委托给 gen1
yield 'b';
yield* [3, 4, 5]; // 委托给可迭代对象
}
const generator = gen2();
console.log([...generator]); // ['a', 1, 2, 'b', 3, 4, 5]
向生成器传递值
next() 方法传值
生成器的 next()
方法可以接受一个参数,这个参数会成为上一个 yield
表达式的值:
function* twoWayGenerator() {
const a = yield 'First yield';
console.log('Received:', a);
const b = yield 'Second yield';
console.log('Received:', b);
return 'Done';
}
const gen = twoWayGenerator();
console.log(gen.next().value); // 'First yield'
console.log(gen.next('Hello').value); // 输出 'Received: Hello',返回 'Second yield'
console.log(gen.next('World').value); // 输出 'Received: World',返回 'Done'
注意:第一次调用 next()
方法时传递的参数会被忽略,因为此时还没有等待中的 yield
表达式。
实际应用:双向通信
function* communicator() {
let message = yield "What's your name?";
let age = yield `Hello, ${message}! How old are you?`;
return `${message} is ${age} years old.`;
}
const conversation = communicator();
console.log(conversation.next().value); // "What's your name?"
console.log(conversation.next('Alice').value); // "Hello, Alice! How old are you?"
console.log(conversation.next(25).value); // "Alice is 25 years old."
生成器的异常处理
throw() 方法
生成器对象的 throw()
方法可以向生成器内部抛出异常:
function* errorHandlingGenerator() {
try {
yield 1;
yield 2;
yield 3;
} catch (error) {
console.log('Caught error:', error.message);
yield 'Error handled';
}
yield 4;
}
const gen = errorHandlingGenerator();
console.log(gen.next().value); // 1
console.log(gen.throw(new Error('Something went wrong')).value); // 输出 'Caught error: Something went wrong',返回 'Error handled'
console.log(gen.next().value); // 4
return() 方法
生成器对象的 return()
方法可以提前结束生成器:
function* countToFive() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
const counter = countToFive();
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
console.log(counter.return('Early end').value); // 'Early end'
console.log(counter.next().value); // undefined,生成器已结束
生成器的实际应用
1. 惰性计算和无限序列
生成器可以表示无限序列,因为它们只在需要时才计算下一个值:
// 无限斐波那契数列
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
// 只获取前 10 个斐波那契数
const fib = fibonacci();
for (let i = 0; i < 10; i++) {
console.log(fib.next().value);
}
2. 简化异步编程
生成器可以与 Promise 结合使用,简化异步编程:
// 模拟异步操作
function fetchData(url) {
return new Promise(resolve => {
setTimeout(() => {
resolve(`Data from ${url}`);
}, 1000);
});
}
// 使用生成器处理异步操作
function* fetchSequentially() {
const result1 = yield fetchData('/api/data1');
console.log(result1);
const result2 = yield fetchData('/api/data2');
console.log(result2);
const result3 = yield fetchData('/api/data3');
console.log(result3);
return 'All done!';
}
// 运行生成器
function run(generatorFunction) {
const generator = generatorFunction();
function handle(result) {
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value)
.then(res => handle(generator.next(res)))
.catch(err => handle(generator.throw(err)));
}
return handle(generator.next());
}
run(fetchSequentially).then(result => {
console.log('Final result:', result);
});
3. 状态机
生成器可以用来实现简洁的状态机:
function* trafficLight() {
while (true) {
yield 'red';
yield 'yellow';
yield 'green';
yield 'yellow';
}
}
const light = trafficLight();
console.log(light.next().value); // 'red'
console.log(light.next().value); // 'yellow'
console.log(light.next().value); // 'green'
console.log(light.next().value); // 'yellow'
console.log(light.next().value); // 'red'(循环继续)
4. 数据转换管道
生成器可以用来创建数据转换管道:
// 生成数字
function* numbers() {
for (let i = 1; i <= 10; i++) {
yield i;
}
}
// 过滤偶数
function* filter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) {
yield item;
}
}
}
// 将每个数字乘以 2
function* map(iterable, mapper) {
for (const item of iterable) {
yield mapper(item);
}
}
// 创建管道
const pipeline = map(
filter(numbers(), n => n % 2 === 0), // 过滤出偶数
n => n * 2 // 将每个偶数乘以 2
);
// 使用管道
for (const num of pipeline) {
console.log(num); // 输出: 4, 8, 12, 16, 20
}
5. 遍历复杂数据结构
生成器可以简化复杂数据结构的遍历:
// 定义树节点
class TreeNode {
constructor(value, left = null, right = null) {
this.value = value;
this.left = left;
this.right = right;
}
// 中序遍历
*inOrderTraversal() {
if (this.left) yield* this.left.inOrderTraversal();
yield this.value;
if (this.right) yield* this.right.inOrderTraversal();
}
// 前序遍历
*preOrderTraversal() {
yield this.value;
if (this.left) yield* this.left.preOrderTraversal();
if (this.right) yield* this.right.preOrderTraversal();
}
// 后序遍历
*postOrderTraversal() {
if (this.left) yield* this.left.postOrderTraversal();
if (this.right) yield* this.right.postOrderTraversal();
yield this.value;
}
}
// 创建一个二叉树
// 1
// / \
// 2 3
// / \
// 4 5
const tree = new TreeNode(1,
new TreeNode(2,
new TreeNode(4),
new TreeNode(5)
),
new TreeNode(3)
);
// 使用不同的遍历方式
console.log('中序遍历:', [...tree.inOrderTraversal()]); // [4, 2, 5, 1, 3]
console.log('前序遍历:', [...tree.preOrderTraversal()]); // [1, 2, 4, 5, 3]
console.log('后序遍历:', [...tree.postOrderTraversal()]); // [4, 5, 2, 3, 1]
生成器方法和属性
生成器函数作为对象方法
生成器函数可以作为对象的方法:
const obj = {
*generator() {
yield 1;
yield 2;
yield 3;
}
};
const gen = obj.generator();
console.log([...gen]); // [1, 2, 3]
生成器函数作为类方法
生成器函数也可以作为类的方法:
class Collection {
constructor(items) {
this.items = items;
}
*[Symbol.iterator]() {
for (const item of this.items) {
yield item;
}
}
*keys() {
for (let i = 0; i < this.items.length; i++) {
yield i;
}
}
*values() {
yield* this.items;
}
*entries() {
for (let i = 0; i < this.items.length; i++) {
yield [i, this.items[i]];
}
}
}
const collection = new Collection(['a', 'b', 'c']);
console.log([...collection]); // ['a', 'b', 'c']
console.log([...collection.keys()]); // [0, 1, 2]
console.log([...collection.values()]); // ['a', 'b', 'c']
console.log([...collection.entries()]); // [[0, 'a'], [1, 'b'], [2, 'c']]
生成器的最佳实践
1. 使用 yield* 委托给其他生成器
function* combined() {
yield* [1, 2, 3];
yield* new Set([4, 5, 6]);
yield* 'hello';
yield* function*() {
yield 7;
yield 8;
}();
}
console.log([...combined()]); // [1, 2, 3, 4, 5, 6, 'h', 'e', 'l', 'l', 'o', 7, 8]
2. 使用 try/finally 进行资源清理
function* withCleanup() {
// 模拟资源获取
const resource = { id: Math.random(), isOpen: true };
console.log('资源已获取:', resource.id);
try {
yield 1;
yield 2;
yield 3;
} finally {
// 无论生成器是否完成,都会执行清理
console.log('资源已释放:', resource.id);
resource.isOpen = false;
}
}
const gen = withCleanup();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.return('提前结束').value); // 输出 '资源已释放: [id]',返回 '提前结束'
3. 避免在生成器中使用箭头函数
生成器函数不能使用箭头函数语法,因为箭头函数没有自己的 this
绑定,也不能作为生成器函数:
// 错误 - 不能使用箭头函数作为生成器
// const arrowGenerator = *() => { yield 1; }; // 语法错误
// 正确 - 使用函数声明或函数表达式
function* correctGenerator() {
yield 1;
}
const correctGeneratorExpression = function*() {
yield 1;
};
4. 使用生成器实现可迭代接口
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
// 使用生成器实现迭代器
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
const range = new Range(1, 5);
console.log([...range]); // [1, 2, 3, 4, 5]
异步生成器
ES2018 引入了异步生成器和异步迭代器,允许在生成器函数中使用 await
关键字,并使用 for await...of
循环迭代异步生成的值。
异步生成器函数
使用 async function*
语法定义异步生成器函数:
async function* asyncGenerator() {
yield Promise.resolve(1);
await new Promise(resolve => setTimeout(resolve, 1000));
yield Promise.resolve(2);
await new Promise(resolve => setTimeout(resolve, 1000));
yield Promise.resolve(3);
}
// 使用异步生成器
async function useAsyncGenerator() {
const gen = asyncGenerator();
console.log((await gen.next()).value); // 1
console.log((await gen.next()).value); // 2(等待 1 秒后)
console.log((await gen.next()).value); // 3(再等待 1 秒后)
}
useAsyncGenerator();
for await...of 循环
使用 for await...of
循环迭代异步可迭代对象:
async function* fetchUrls(urls) {
for (const url of urls) {
const response = await fetch(url);
const data = await response.json();
yield data;
}
}
async function processUrls() {
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3'
];
// 使用 for await...of 循环
for await (const data of fetchUrls(urls)) {
console.log('处理数据:', data);
}
}
processUrls().catch(error => {
console.error('发生错误:', error);
});
实际应用:分页 API 请求
async function* fetchAllPages(baseUrl, pageSize = 10) {
let page = 1;
let hasMore = true;
while (hasMore) {
const url = `${baseUrl}?page=${page}&pageSize=${pageSize}`;
const response = await fetch(url);
const data = await response.json();
yield data.items;
hasMore = data.hasNextPage;
page++;
}
}
async function getAllItems() {
const allItems = [];
for await (const items of fetchAllPages('https://api.example.com/items')) {
console.log(`获取了 ${items.length} 个项目`);
allItems.push(...items);
}
console.log(`总共获取了 ${allItems.length} 个项目`);
return allItems;
}
getAllItems().catch(error => {
console.error('获取数据时出错:', error);
});
生成器与协程
生成器函数可以看作是 JavaScript 中协程的一种实现。协程是一种比线程更轻量级的存在,允许在特定的位置暂停和恢复执行。
生成器作为协程
function* task1() {
for (let i = 0; i < 3; i++) {
console.log('Task 1 step', i);
yield; // 让出控制权
}
}
function* task2() {
for (let i = 0; i < 3; i++) {
console.log('Task 2 step', i);
yield; // 让出控制权
}
}
// 简单的协程调度器
function runTasks(tasks) {
const generators = tasks.map(task => task());
let completed = 0;
while (completed < generators.length) {
for (let i = 0; i < generators.length; i++) {
const result = generators[i].next();
if (result.done) {
completed++;
}
}
}
}
runTasks([task1, task2]);
// 输出:
// Task 1 step 0
// Task 2 step 0
// Task 1 step 1
// Task 2 step 1
// Task 1 step 2
// Task 2 step 2
总结
生成器函数是 JavaScript 中一个强大而灵活的特性,它提供了一种创建迭代器的简洁方式,并允许函数在执行过程中暂停和恢复。生成器的主要优点包括:
- 简化迭代器创建:生成器提供了一种简洁的语法来创建复杂的迭代器
- 惰性计算:只在需要时才计算值,提高效率
- 双向通信:可以通过
next()
方法向生成器发送值 - 异常处理:可以使用
throw()
方法向生成器内部抛出异常 - 资源管理:可以使用
return()
方法提前结束生成器并进行清理 - 异步支持:异步生成器可以与 Promise 和 async/await 无缝集成
生成器在处理序列、实现状态机、创建数据转换管道、遍历复杂数据结构和简化异步编程等方面有广泛的应用。通过掌握生成器,可以编写更简洁、更高效、更易于维护的 JavaScript 代码。
在下一章中,我们将探讨异步迭代器和异步生成器的更多高级用法,以及它们在现代 JavaScript 应用程序中的实际应用。