ZetCode

TypeScript Generator

最后修改时间:2025年3月3日

TypeScript 中的 Generator 是一种特殊的函数,它们按需生成值序列。它们使用 function* 语法和 yield 关键字来暂停和恢复执行。本教程将通过实际示例探讨 Generator,帮助您掌握它们的用法。

基本 Generator 语法

Generator 使用 function* 定义。yield 关键字会暂停执行并返回一个值。此示例演示了一个简单的 Generator。

basic_generator.ts
function* simpleGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const generator = simpleGenerator();
console.log(generator.next().value); // Output: 1
console.log(generator.next().value); // Output: 2
console.log(generator.next().value); // Output: 3

在此示例中,simpleGenerator 使用 function* 语法定义,将其标记为 Generator。每次在 Generator 对象上调用 next 时,yield 关键字都会暂停执行并返回一个值。第一次调用 next 会执行到 yield 1,返回 1;后续调用会从上次暂停点恢复,依次产生 2 和 3。

在最后一个 yield 之后,进一步调用 next 将返回 { value: undefined, done: true },表示 Generator 已耗尽。这展示了 Generator 的核心机制:惰性地、顺序地生成值。

无限序列

Generator 可以生成无限序列。此示例生成一个无限的偶数序列。

infinite_generator.ts
function* evenNumbers() {
    let num = 0;
    while (true) {
        yield num;
        num += 2;
    }
}

const evens = evenNumbers();
console.log(evens.next().value); // Output: 0
console.log(evens.next().value); // Output: 2
console.log(evens.next().value); // Output: 4

evenNumbers Generator 使用 while (true) 循环来创建无限的偶数序列,从 0 开始,每次 yield 递增 2。与一次计算所有值的传统函数不同,此 Generator 按需生成值:每次调用 next 都会恢复循环,产生当前的 num,然后再次暂停。输出显示了 0、2 和 4,但该序列可以无限延续。这突显了 Generator 在不消耗无限内存的情况下处理潜在无限数据以及实现惰性求值的能力。

向 Generator 传递值

Generator 可以通过 next 接收值。此示例演示了向 Generator 传递值。

passing_values.ts
function* valueReceiver() {
    const name = yield "What is your name?";
    const age = yield `Hello, ${name}! How old are you?`;
    return `You are ${age} years old.`;
}

const receiver = valueReceiver();
console.log(receiver.next().value); // Output: What is your name?
console.log(receiver.next("Alice").value); // Output: Hello, Alice! How old are you?
console.log(receiver.next(30).value); // Output: You are 30 years old.

valueReceiver Generator 展示了双向通信:它产生提示,并通过 next 接收值。第一次调用 next 会产生“What is your name?”并暂停。第二次调用 next("Alice") 会将“Alice”传递给 yield 表达式,将其赋值给 name,然后使用该值产生下一个提示。最后,next(30) 将 30 赋值给 age,并返回一个最终字符串,结束 Generator。输出反映了这种交互流程,展示了 Generator 如何充当有状态的、会话式的迭代器,每次调用 next 都会根据外部输入推进内部逻辑。

组合 Generator

Generator 可以使用 yield* 委托给其他 Generator。此示例组合了两个 Generator。

combining_generators.ts
function* firstGenerator() {
    yield 1;
    yield 2;
}

function* secondGenerator() {
    yield* firstGenerator();
    yield 3;
}

const combined = secondGenerator();
console.log(combined.next().value); // Output: 1
console.log(combined.next().value); // Output: 2
console.log(combined.next().value); // Output: 3

在此,secondGenerator 使用 yield* 委托给 firstGenerator,有效地将其序列(1、2)嵌入到自己的序列中。当在 combined 上调用 next 时,它首先按顺序产生 firstGenerator 的值(1,然后是 2),然后继续产生其自身体内的 3。yield* 语法将控制无缝地转移到被委托的 Generator,直到其耗尽,然后恢复父 Generator 的执行。输出(1、2、3)显示了这种可组合性如何允许 Generator 从更简单的 Generator 构建复杂的序列,从而增强了模块化和可重用性。

错误处理

Generator 可以使用 throw 处理错误。此示例展示了 Generator 中的错误处理。

error_handling.ts
function* errorGenerator() {
    try {
        yield 1;
        yield 2;
    } catch (error) {
        yield `Error: ${error}`;
    }
}

const errorGen = errorGenerator();
console.log(errorGen.next().value); // Output: 1
console.log(errorGen.throw("Something went wrong").value); // Output: Error: Something went wrong

errorGenerator 将其 yield 包装在 try-catch 块中以管理错误。第一个 next 正常产生 1。然后,throw("Something went wrong") 在最后一个 yield 的位置注入一个错误,该错误被 catch 块捕获,产生一个格式化的错误消息而不是崩溃。

后续调用 next 将表示 Generator 已完成。此示例说明了 throw 如何实现外部错误信号,以及 try-catch 如何确保健壮的错误处理,使 Generator 对中断具有弹性,同时保持其迭代流程。

从 Generator 返回

Generator 可以使用 return 返回最终值。此示例演示了返回值。

return_generator.ts
function* returnGenerator() {
    yield 1;
    yield 2;
    return "Done";
}

const returnGen = returnGenerator();
console.log(returnGen.next().value); // Output: 1
console.log(returnGen.next().value); // Output: 2
console.log(returnGen.next().value); // Output: Done

returnGenerator 中,return "Done" 语句提供了一个最终值并终止了 Generator。前两次 next 调用通过 yield 产生 1 和 2,而第三次调用到达 return,内部产生“Done”并返回 { value: "Done", done: true }(尽管此处仅记录了值)。

与暂停的 yield 不同,return 会结束 Generator,发出完成信号。此示例展示了如何使用 return 以有意义的结果结束序列,提供一个与惰性产生过程不同的清晰终点。

异步 Generator

异步 Generator 将 Generator 与异步操作结合起来。此示例展示了一个异步 Generator。

async_generator.ts
async function* asyncGenerator() {
    yield await Promise.resolve(1);
    yield await Promise.resolve(2);
    yield await Promise.resolve(3);
}

(async () => {
    const asyncGen = asyncGenerator();
    console.log(await asyncGen.next()); // Output: { value: 1, done: false }
    console.log(await asyncGen.next()); // Output: { value: 2, done: false }
    console.log(await asyncGen.next()); // Output: { value: 3, done: false }
})();

asyncGenerator 使用 async function* 定义一个异步 Generator,将 yieldawait 结合起来处理来自 Promise.resolve 的异步值。每次 yield 都会等待 Promise 解析,然后暂停并返回该值(1、2、3)。

带有 await on next 的 IIFE(立即调用函数表达式)会检索每个结果,并记录完整的迭代器对象({ value, done })。输出显示了一系列已解析的值,done: false,并且第四次调用将返回 { value: undefined, done: true }。这表明异步 Generator 如何惰性地管理异步数据流,非常适合增量获取数据等任务。

带类型注解的 Generator

TypeScript 允许为 Generator 添加类型注解以提高类型安全性。此示例展示了一个类型化的 Generator。

typed_generator.ts
function* numberGenerator(): Generator {
    yield 1;
    yield 2;
    return "Finished";
}

const numGen = numberGenerator();
console.log(numGen.next().value); // Output: 1
console.log(numGen.next().value); // Output: 2
console.log(numGen.next().value); // Output: Finished

numberGenerator 使用 Generator 类型注解,指定 number 作为产生的值的类型,string 作为返回值的类型,以及 void 作为 yield 接收的值的类型(此处为空)。这确保 TypeScript 强制执行 yield 产生数字,return 产生字符串,并在编译时捕获类型不匹配。next 调用产生 1 和 2,然后返回“Finished”,与注解匹配。输出确认了序列,显示了类型注解如何通过静态类型检查增强 Generator,从而提高代码的可靠性和可维护性。

惰性斐波那契序列

Generator 非常适合对 Fibonacci 数等序列进行惰性求值。此示例按需生成 Fibonacci 数。

fibonacci_generator.ts
function* fibonacci() {
    let a = 0, b = 1;
    while (true) {
        yield a;
        [a, b] = [b, a + b];
    }
}

const fib = fibonacci();
console.log(fib.next().value); // Output: 0
console.log(fib.next().value); // Output: 1
console.log(fib.next().value); // Output: 1
console.log(fib.next().value); // Output: 2

fibonacci Generator 惰性地创建了一个无限的 Fibonacci 序列。它将 ab 初始化为 0 和 1,每次产生 a,并使用数组解构更新对以计算下一个数字(b,然后是 a + b)。每次调用 next 都会产生下一个 Fibonacci 数——0、1、1、2,依此类推——而无需预先计算整个序列。输出显示了前四个值,展示了 Generator 如何仅在需要时计算值来高效地处理复杂序列,从而节省内存并支持无限序列。

带提前终止的 Generator

Generator 可以使用 return 提前终止。此示例演示了受控终止。

early_termination.ts
function* countDown(n: number) {
    while (n > 0) {
        yield n;
        n--;
    }
}

const counter = countDown(3);
console.log(counter.next().value); // Output: 3
console.log(counter.return(0).value); // Output: 0
console.log(counter.next().value); // Output: undefined

countDown Generator 从 n 递减到 1,但 return(0) 允许提前终止。第一个 next 产生 3,然后 return(0) 停止 Generator,返回 0 并内部返回 { value: 0, done: true }。后续调用 next 会产生 undefined,因为 Generator 已完成。这与内部 return 语句不同,因为 return 是一种外部控制机制。输出显示了此方法如何提供灵活性以提前停止 Generator,这对于需要动态控制迭代的场景很有用。

最佳实践

来源

TypeScript Generator 文档

本教程通过实际示例涵盖了 TypeScript Generator。使用这些模式来创建高效且可维护的代码。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。至今,我已撰写了 1,400 多篇文章和 8 本电子书。我在教授编程方面拥有超过十年的经验。

列出所有 TypeScript 教程