优秀的前辈往往是我们道路上的灯塔。
本文作者Debjyoti Banerjee是一名软件工程师和游戏开发人员。目前正在探索JavaScript和Flutter,热爱开源。
接下来,我们一起分享一下 Debjyoti Banerjee 对 TypeScript generators 深刻理解。
了解 TypeScript generators
正常功能从上到下运行,然后退出。生成器函数也从上到下运行,但它们可以在执行期间暂停,稍后从同一点恢复。这种情况一直持续到过程结束,然后他们退出。在本文中,我们将学习如何在 TypeScript 中使用生成器函数,涵盖几个不同的示例和用例。让我们开始吧!
在 TypeScript 中创建生成器函数
普通函数是快速的,而生成器是懒惰的,这意味着它们可以被要求在以后的时间点执行。要创建生成器函数,我们将使用function *
命令。生成器函数看起来像普通函数,但它们的行为略有不同。请看以下示例:
function normalFunction() {
console.log("This is a normal function");
}
function* generatorFunction() {
console.log("This is a generator function");
}
normalFunction(); // "This is a normal function"
generatorFunction();
虽然它的编写和执行就像普通函数一样,但在调用generatorFunction
时,我们不会在控制台中获得任何日志。简而言之,调用生成器不会执行代码:
您会注意到生成器函数返回一个Generator
类型;我们将在下一节中详细讨论这一点。为了使生成器执行我们的代码,我们将执行以下操作:
function* generatorFunction() {
console.log("This is a generator function");
}
const a = generatorFunction();
a.next();
请注意,next
方法返回一个对象IteratorResult
,因此如果我们要从IteratorResult
返回一个数字,我们将按如下方式访问该值:
function* generatorFunction() {
console.log("This is a generator function");
return 3;
}
const a = generatorFunction();
const b = a.next();
console.log(b); // {"value": 3, "done": true}
console.log(b.value); // 3
生成器接口继承Iterator
,这允许我们调用next
.它还具有[Symbol.iterator]
属性,使其成为可迭代对象。
了解 JavaScript 可迭代对象和迭代器
可迭代对象是可以使用 for 进行迭代的对象。的
。他们必须实施Symbol.iterator
方法;例如,JavaScript 中的数组是内置的可迭代对象,因此它们必须具有迭代器:Symbol.iterator
const a = [1,2,3,4];
const it: Iterator<number> = a[Symbol.iterator]();
while (true) {
let next = it.next()
if (!next.done) {
console.log(next.value)
} else {
break;
}
}
迭代器使迭代可迭代对象成为可能。看看下面的代码,这是一个非常简单的迭代器实现:
function naturalNumbers() {
let n = 0;
return {
next: function() {
n += 1;
return {value:n, done:false};
}
};
}
const iterable = naturalNumbers();
iterable.next().value; // 1
iterable.next().value; // 2
iterable.next().value; // 3
iterable.next().value; // 4
如上所述,可迭代对象是具有Symbol.iterator
属性的对象。因此,如果我们要分配一个返回next()
函数的函数,就像上面的例子一样,我们的对象将成为一个 JavaScript 可迭代对象。然后,我们可以使用for..of
语法对其进行迭代。
显然,我们之前看到的生成器函数与上面的例子之间存在相似之处。事实上,由于生成器一次计算一个值,我们可以轻松地使用生成器来实现迭代器。
在 TypeScript 中使用生成器
生成器令人兴奋的事情是,您可以使用yield
语句暂停执行,我们在前面的示例中没有这样做。调用next
时,生成器同步执行代码,直到遇到yield
,此时它会暂停执行。如果再次调用next
,它将从暂停的位置恢复执行。让我们看一个例子:
function* iterator() {
yield 1
yield 2
yield 3
}
for(let x of iterator()) {
console.log(x)
}
yield
basically allows us to return multiple times from the function. In addition, an array will never be created in memory, allowing us to create infinite sequences in a very memory efficient manner. The following example will generate infinite even numbers:
function* evenNumbers() {
let n = 0;
while(true) {
yield n += 2;
}
}
const gen = evenNumbers();
console.log(gen.next().value); //2
console.log(gen.next().value); //4
console.log(gen.next().value); //6
console.log(gen.next().value); //8
console.log(gen.next().value); //10
We can also modify the example above so that it takes a parameter and yields even numbers, starting from the number provided:
function* evenNumbers(start: number) {
let n = start;
while(true) {
if (start === 0) {
yield n += 2;
} else {
yield n;
n += 2;
}
}
}
const gen = evenNumbers(6);
console.log(gen.next().value); //6
console.log(gen.next().value); //8
console.log(gen.next().value); //10
console.log(gen.next().value); //12
console.log(gen.next().value); //14
Use cases for TypeScript generators
生成器提供了一种强大的机制,用于控制数据流并在 TypeScript 中创建灵活、高效和可读的代码。它们能够按需生成值、处理异步操作和创建自定义迭代逻辑,使其成为少数方案中的宝贵工具。
按需计算值
您可以实现生成器来按需计算和生成值,缓存中间结果以提高性能。在处理昂贵的计算或延迟某些操作的执行直到实际需要它们时,此技术非常有用。让我们考虑以下示例:
function* calculateFibonacci(): Generator<number> {
let prev = 0;
let curr = 1;
yield prev;
yield curr;
while (true) {
const next = prev + curr;
yield next;
prev = curr;
curr = next;
}
}
// Using the generator to calculate Fibonacci numbers lazily
const fibonacciGenerator = calculateFibonacci();
// Calculate the first 10 Fibonacci numbers
for (let i = 0; i < 10; i++) {
console.log(fibonacciGenerator.next().value);
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
}
在上面的示例中,不是预先计算所有斐波那契数,而是仅计算所需的斐波那契数,并根据请求生成。这样可以提高内存使用效率,并根据需要按需计算值。
迭代大型数据集
生成器允许您迭代大型数据集,而无需一次将所有数据加载到内存中。相反,您可以根据需要生成值,从而提高内存效率。这在处理大型数据库或文件时特别有用:
function* iterateLargeData(): Generator<number> {
const data = Array.from({ length: 1000000 }, (_, index) => index + 1);
for (const item of data) {
yield item;
}
}
// Using the generator to iterate over the large data set
const dataGenerator = iterateLargeData();
for (const item of dataGenerator) {
console.log(item);
// Perform operations on each item without loading all data into memory
}
在此示例中,iterateLargeData
生成器函数通过创建包含一百万个数字的数组来模拟大型数据集。生成器不是一次返回整个数组,而是使用yield
关键字一次生成一个项目。因此,您可以迭代数据集,而无需将所有数字同时加载到内存中。
递归使用生成器
生成器的内存高效属性可以用于更有用的事情,例如递归读取目录中的文件名。事实上,递归遍历嵌套结构是我在考虑生成器时很自然的事情。
由于yield
是一个表达式,yield*
可用于委托给另一个可迭代对象,如以下示例所示:
function* readFilesRecursive(dir: string): Generator<string> {
const files = fs.readdirSync(dir, { withFileTypes: true });
for (const file of files) {
if (file.isDirectory()) {
yield* readFilesRecursive(path.join(dir, file.name));
} else {
yield path.join(dir, file.name);
}
}
}
我们可以按如下方式使用我们的函数:
for (const file of readFilesRecursive('/path/to/directory')) {
console.log(file);
}
我们还可以用来将值传递给生成器。请看以下示例:yield
function* sumNaturalNumbers(): Generator<number, any, number> {
let value = 1;
while(true) {
const input = yield value;
value += input;
}
}
const it = sumNaturalNumbers();
it.next();
console.log(it.next(2).value); //3
console.log(it.next(3).value); //6
console.log(it.next(4).value); //10
console.log(it.next(5).value); //15
当被调用时,被赋值;同样,当被调用时,输入被分配值。next(2)``input``2``next(3)``3
错误处理
如果要使用生成器,异常处理和控制执行流是一个重要的概念。生成器基本上看起来像普通函数,所以语法是一样的。
当生成器遇到错误时,它可以使用throw
key 引发异常。可以使用 try… 捕获和处理此异常...抓住
在发生器功能内部或消耗发生器时在外部阻止:
function* generateValues(): Generator<number, void, string> {
try {
yield 1;
yield 2;
throw new Error('Something went wrong');
yield 3; // This won't be reached
} catch (error) {
console.log("Error caught");
yield* handleError(error); // Handle the error and continue
}
}
function* handleError(error: Error): Generator<number, void, string> {
yield 0; // Continue with a default value
yield* generateFallbackValues(); // Yield fallback values
throw `Error handled: ${error.message}`; // Throw a new error or rethrow the existing one
}
const generator = generateValues();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // Error caught
// { value: 0, done: false }
console.log(generator.next()); // { value: 4, done: false }
console.log(generator.next()); // Error handled: Something went wrong
在此示例中,generateValues
生成器函数在生成值2
后引发错误。生成器内的 catch 块捕获错误,并将控制权传输到handleError
生成器函数,从而生成回退值。最后,handleError
函数抛出一个新错误或重新抛出现有错误。
使用生成器时,您也可以使用try...catch
块捕获抛出的错误:
const generator = generateValues();
try {
console.log(generator.next());
console.log(generator.next());
console.log(generator.next());
} catch (error) {
console.error('Caught error:', error);
}
在这种情况下,错误将被 catch 块捕获,您可以相应地处理它。