引入:迭代器和生成器的概念
在深入探讨 yield 关键字之前,我们有必要先了解一些基本概念。迭代器(Iterator)是一种设计模式,用于遍历集合中的元素,而无需暴露其底层表示形式。ES6 引入了内置的迭代器接口,使开发者可以更容易地实现这一模式。
生成器(Generator)是另一种特殊的函数类型,允许在执行过程中暂停并恢复。这使得它们非常适合处理需要逐步计算或异步操作的数据流。生成器通过 function* 语法定义,并使用 yield 关键字来控制函数的暂停和恢复。
yield 的基本用法
定义生成器函数
生成器函数通过在 function 关键字后面添加一个星号(*)来定义。这与普通函数不同,生成器函数可以返回多个值,这些值通过 yield 关键字传递。
function* myGenerator() {
yield "Hello";
yield "World";
}使用生成器
生成器对象可以通过调用生成器函数来创建。一旦生成器被创建,你可以通过调用 .next() 方法来获取下一个生成的值。当遇到 yield 关键字时,函数将暂停,并返回一个包含当前 value 和 done 属性的对象。
const generator = myGenerator();
console.log(generator.next()); // { value: "Hello", done: false }
console.log(generator.next()); // { value: "World", done: false }
console.log(generator.next()); // { value: undefined, done: true }yield 与返回值
单次返回
生成器函数也可以像普通函数一样返回一个值。这个返回值会被作为生成器对象的 return 方法的结果。当调用 .return() 方法时,生成器会立即停止执行,并返回指定的值。
// www.javascriptcn.com code example
function* myGenerator() {
yield "First";
return "Final Value";
}
const generator = myGenerator();
console.log(generator.next()); // { value: "First", done: false }
console.log(generator.next()); // { value: "Final Value", done: true }
console.log(generator.return("Manually returned")); // { value: "Manually returned", done: true }多次返回
尽管生成器函数只允许一次返回值,但你可以在生成器内部多次调用 .return() 方法。后续的 .return() 调用不会改变先前返回值的效果。
// www.javascriptcn.com code example
function* myGenerator() {
yield "One";
return "Two";
yield "Three"; // This will not be reached
}
const generator = myGenerator();
console.log(generator.next()); // { value: "One", done: false }
console.log(generator.next()); // { value: "Two", done: true }
console.log(generator.next()); // { value: undefined, done: true }yield 与错误处理
抛出异常
生成器函数可以通过调用 throw 方法来抛出异常。这会使生成器暂停执行,并在生成器内部引发异常。如果未捕获,则生成器将继续执行直到完成。
// www.javascriptcn.com code example
function* myGenerator() {
try {
yield "Start";
throw new Error("An error occurred");
yield "End";
} catch (error) {
console.error(error.message); // "An error occurred"
yield "Recovered";
}
}
const generator = myGenerator();
console.log(generator.next()); // { value: "Start", done: false }
console.log(generator.throw(new Error("Error thrown"))); // { value: "Recovered", done: false }捕获异常
生成器函数可以通过在生成器内部使用 try...catch 结构来捕获并处理异常。这有助于在生成器暂停和恢复执行时进行错误处理。
// www.javascriptcn.com code example
function* myGenerator() {
try {
yield "First";
yield "Second";
throw new Error("Unexpected error");
} catch (error) {
console.error(error.message); // "Unexpected error"
yield "Recovered";
}
}
const generator = myGenerator();
console.log(generator.next()); // { value: "First", done: false }
console.log(generator.next()); // { value: "Second", done: false }
console.log(generator.throw(new Error("Error thrown"))); // { value: "Recovered", done: false }yield 与 for-of 循环
遍历生成器
生成器对象可以与 for-of 循环结合使用,以便更方便地遍历生成器产生的序列。for-of 循环会自动调用生成器的 .next() 方法,直到生成器完成。
// www.javascriptcn.com code example
function* myGenerator() {
yield "Item One";
yield "Item Two";
yield "Item Three";
}
for (let item of myGenerator()) {
console.log(item);
}
// 输出:
// Item One
// Item Two
// Item Three使用生成器作为数据源
生成器不仅可以用作简单的数据生成器,还可以用于处理复杂的数据流。例如,可以使用生成器来读取文件、处理网络请求等。
// www.javascriptcn.com code example
async function* readLines(filePath) {
const fileStream = fs.createReadStream(filePath, 'utf8');
let content = '';
for await (const chunk of fileStream) {
content += chunk;
const lines = content.split('\n');
if (lines.length > 1) {
yield lines.shift();
}
content = lines.pop();
}
if (content) {
yield content;
}
}
(async () => {
for await (const line of readLines('example.txt')) {
console.log(line);
}
})();yield 与并发编程
协程与并发
生成器可以用来实现协程(Coroutine),这是一种轻量级的并发编程模型。通过使用生成器和 yield 关键字,可以实现非阻塞的并发操作。
// www.javascriptcn.com code example
function* coroutine() {
const result1 = yield asyncFunction1();
const result2 = yield asyncFunction2(result1);
const result3 = yield asyncFunction3(result2);
return result3;
}
function asyncFunction1() {
return new Promise(resolve => setTimeout(() => resolve("Result 1"), 1000));
}
function asyncFunction2(value) {
return new Promise(resolve => setTimeout(() => resolve(`Result 2 with ${value}`), 500));
}
function asyncFunction3(value) {
return new Promise(resolve => setTimeout(() => resolve(`Result 3 with ${value}`), 750));
}
const gen = coroutine();
function step() {
const { value, done } = gen.next();
if (!done) {
value.then(result => {
gen.next(result);
setTimeout(step, 0);
});
}
}
setTimeout(step, 0);yield 与惰性求值
惰性求值
生成器的一个强大特性是它们支持惰性求值(Lazy Evaluation)。这意味着只有在需要时才会计算生成器中的值,从而提高性能并减少不必要的计算。
// www.javascriptcn.com code example
function* generateSequence() {
for (let i = 0; i < 10; i++) {
yield i * i;
}
}
const sequence = generateSequence();
console.log(sequence.next().value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 4
// 只有在实际需要时才计算下一个值yield 与其他语言的比较
ECMAScript 与 Python
虽然 yield 关键字在 JavaScript 中引入,但它与 Python 中的 yield 类似,都用于生成器函数。然而,两者在语法和实现上有一些细微差别。
Python:
def my_generator(): yield "Hello" yield "World" generator = my_generator() print(next(generator)) # Hello print(next(generator)) # WorldJavaScript:
function* myGenerator() { yield "Hello"; yield "World"; } const generator = myGenerator(); console.log(generator.next().value); // Hello console.log(generator.next().value); // World
ECMAScript 与 C#
C# 中的迭代器
C# 也支持类似的功能,通过 yield return 来实现。虽然语法有所不同,但基本概念是相同的。
// www.javascriptcn.com code example
public IEnumerable<string> MyGenerator() {
yield return "Hello";
yield return "World";
}
var generator = MyGenerator();
foreach (var item in generator) {
Console.WriteLine(item);
}
// 输出:
// Hello
// Worldyield 与异步编程
异步生成器
随着异步编程的流行,JavaScript 引入了异步生成器(Async Generators),它结合了 async 和 yield 的功能,允许生成器在等待异步操作的同时继续产生值。
// www.javascriptcn.com code example
async function* asyncGenerator() {
yield "First";
yield "Second";
await new Promise(resolve => setTimeout(resolve, 1000));
yield "Third";
}
(async () => {
const gen = asyncGenerator();
console.log(await gen.next()); // { value: "First", done: false }
console.log(await gen.next()); // { value: "Second", done: false }
await new Promise(resolve => setTimeout(resolve, 1000));
console.log(await gen.next()); // { value: "Third", done: false }
})();并发控制
生成器和异步生成器还支持并发控制,例如使用 Promise.all 或 Promise.race 来管理多个异步操作。
// www.javascriptcn.com code example
async function* concurrentOperations() {
const promises = [
asyncFunction1(),
asyncFunction2(),
asyncFunction3()
];
const results = await Promise.all(promises.map(promise => promise.catch(e => e)));
for (const result of results) {
yield result;
}
}
async function asyncFunction1() {
return new Promise(resolve => setTimeout(() => resolve("Result 1"), 1000));
}
async function asyncFunction2() {
return new Promise(resolve => setTimeout(() => resolve("Result 2"), 500));
}
async function asyncFunction3() {
return new Promise(resolve => setTimeout(() => resolve("Result 3"), 750));
}
(async () => {
const gen = concurrentOperations();
console.log(await gen.next());
console.log(await gen.next());
console.log(await gen.next());
})();总结
通过本章的学习,你应该对 yield 关键字有了深入的理解。从基本用法到高级技巧,生成器为处理复杂数据流提供了强大的工具。无论是处理文件、网络请求,还是实现并发控制,生成器都是不可多得的好帮手。希望你能够在未来项目中灵活运用这些知识!