??? TypeScript generators 生成器函数

优秀的前辈往往是我们道路上的灯塔。

本文作者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 块捕获,您可以相应地处理它。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYi8wyIw' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片