【问题标题】:Reusing a Generator in Multiple Functions/Methods在多个函数/方法中重用生成器
【发布时间】:2021-06-19 16:26:17
【问题描述】:

我正在尝试创建一个文件读取器对象(来自 readFileSync)并提供来自生成器函数的行。我的意图是将此生成器对象传递给多个函数并按顺序解析文件。但是,在单个函数中使用生成器后,生成器的状态会从挂起变为关闭。我来自 Python 背景,这是 Python 中非常可能的操作。想知道我在这里做错了什么。以下是我使用的代码:

生成器函数定义(我使用的是 readFileSync,它不是异步的,请暂时忽略它,因为我正在尝试让生成器工作):

function* getFileGen(path: string){
  const fileContent = fs
  .readFileSync(path, {
    encoding: "utf-8",
    flag: "r",
  })
  .split("\n");

  while(true){
      const thisLine = fileContent.shift();
      if(!thisLine){
        break;
      }
      yield thisLine; 
  }
}

我想在其中使用生成器的两个函数:

function getFirstFew(stream: Generator){
  let i = 0;
  for(let v of stream){
    console.log(v);
    if(i > 1){
      break;
    }
    i++;
  }
}

function getNextFew(stream: Generator){
  let i = 0;
  for(let v of stream){
    console.log(v);
    if(i > 7){
      break;
    }
    i++;
  }

最后创建一个生成器并将其依次传递给两个会打印多行的函数:

const myStream = getFileGen('path/to/file');

getFirstFew(myStream);
getNextFew(myStream);

第一个函数正确执行并打印 3 行;但是当生成器被传递给 getNextFew 函数时,它已经关闭了。

【问题讨论】:

    标签: javascript typescript ecmascript-6 iterator generator


    【解决方案1】:

    来自docs

    在 for...of 循环中,突然的迭代终止可能由 break、throw 或 return 引起。在这些情况下,迭代器关闭

    特别是:

    不要重复使用生成器

    不应重复使用生成器,即使 for...of 循环提前终止,例如通过 break 关键字。退出循环后,生成器将关闭,尝试再次对其进行迭代不会产生任何进一步的结果。

    强调我的。

    不过,我承认,我对 JS 不是很擅长,所以我不能推荐类似的解决方法。您可能需要使用您可以控制更多的列表或其他严格的结构。

    您也许可以实现与 Python 的 tee 类似的 tee 函数,该函数创建迭代器的副本,然后迭代其中一个副本。

    【讨论】:

    • 感谢您的回答。实际上,在我查看您的答案之前,我怀疑 for of 应该是问题所在,并使用了类似 while(line = this.stream.next().value){ ... //line} 的东西,而无需终止生成器。
    • @c00der 使用这个想法,您可以创建一个take_n 函数,该函数从迭代器中提取n 对象并返回它们。如果我在你的位置上,我会召集一些帮手。这实际上听起来像是一个寒冷的下午迷你项目。
    • 那将是一个很酷的主意。会试一试。实际上,在我正在实现的版本中,文件中只有几行由一个函数处理,其余的由另一个函数处理。
    【解决方案2】:

    这是一个很棒的 Node 流 API 应用程序。

    你可以use a generator as a source for a Node.js ReadableStream,然后duplicate it using PassThrough

    这里有点像这个答案:Node.js Piping the same readable stream into multiple (writable) targets

    基本上你可以这样做:

    const { PassThrough, Readable } = require("stream");
    
    function* getFileGen(path: string) {
      const fileContent = fs
      .readFileSync(path, {
        encoding: "utf-8",
        flag: "r",
      })
      .split("\n");
    
      while(true) {
          const thisLine = fileContent.shift();
          if(!thisLine) {
            break;
          }
          yield thisLine; 
      }
    }
    
    // Set up per: https://nodejs.org/api/stream.html#streamreadablefromiterable-options
    const fileReadlineStream = Readable.from(getFileGen(), { objectMode: false });
    
    const firstFew = new PassThrough();
    const nextFew = new PassThrough();
    
    fileReadlineStream.pipe(firstFew);
    fileReadlineStream.pipe(nextFew);
    
    firstFew.on("data", (d) => {
        console.log("First few received:", d);
    });
    
    nextFew.on("data", (d) => {
        console.log("Next few received:", d);
    });
    

    如果您想以某种方式转换该文件,最好使用 TransformStream。在执行过程中,您可能希望将一个流的处理优先于另一个;我不是确切行为的专家,但通常你可以handle that with buffering

    【讨论】:

      【解决方案3】:

      for of 循环总是通过在最后调用.return() 来关闭迭代器(如果该方法存在)。您可以通过删除(覆盖)方法来防止这种情况

      const myStream = getFileGen('path/to/file');
      myStream.return = undefined;
      getFirstFew(myStream);
      getNextFew(myStream);
      

      但这很粗糙。我宁愿做类似的事情

      function keptOpen(iterable) {
          const iterator = iterable[Symbol.iterator]()
          return {
              [Symbol.iterator]() { return this; },
              next(v) { return iterator.next(); },
          };
      }
      

      并像使用它

      const myStream = keptOpen(getFileGen('path/to/file'));
      getFirstFew(myStream);
      getNextFew(myStream);
      

      但是,请注意,这仅适用于不关心保持打开状态的生成器函数。如果他们期望调用他们的.return() 方法以便他们可以处理分配的资源,keptOpen 将使他们泄漏。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-02-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-07-12
        • 2021-12-28
        • 2012-12-03
        • 2015-06-02
        相关资源
        最近更新 更多