Highland.js中的嵌套流操作

时间:2014-12-31 12:37:08

标签: node.js stream highland.js

我有一个来自readdirp模块的目录流。

我想: -

  • 使用每个目录中的正则表达式(例如README.*)搜索文件
  • 读取该文件的第一行,该行不以#
  • 开头
  • 打印出目录中每个目录和README的第一个非标题行。

我正在尝试使用流和highland.js

我很难尝试处理每个目录中的所有文件流。

h = require 'highland'

dirStream = readdirp root: root, depth: 0, entryType: 'directories'

dirStream = h(dirStream)
  .filter (entry) -> entry.stat.isDirectory()
  .map (entry) ->

    # Search all files in the directory for README.
    fileStream = readdirp root: entry.fullPath, depth: 0, entryType: 'files', fileFilter: '!.DS_Store'
    fileStream = h(fileStream).filter (entry) -> /README\..*/.test entry.name
    fileStream.each (file) ->
      readmeStream = fs.createReadStream file
      _(readmeStream)
        .split()
        .takeUntil (line) -> not line.startsWith '#' and line isnt ''
        .last(1)
        .toArray (comment) ->
          # TODO: How do I access `comment` asynchronously to include in the return value of the map?

    return {name: entry.name, comment: comment}

1 个答案:

答案 0 :(得分:4)

最好将Highland流视为不可变的,并且filtermap等操作返回依赖于旧流的新流,而不是旧流的修改。

此外,Highland方法很懒惰:当您绝对需要数据时,您应该只调用eachtoArray

异步映射流的标准方法是flatMap。它就像map,但你给它的功能应该返回一个流。从flatMap获得的流是所有返回流的串联。因为新流依次依赖于所有旧流,所以它可用于对异步过程进行排序。

我将您的示例修改为以下内容(澄清了一些变量名称):

h = require 'highland'

readmeStream = h(readdirp root: root, depth: 0, entryType: 'directories')
  .filter (dir) -> dir.stat.isDirectory()
  .flatMap (dir) ->
    # Search all files in the directory for README.
    h(readdirp root: dir.fullPath, depth: 0, entryType: 'files', fileFilter: '!.DS_Store')
    .filter (file) -> /README\..*/.test file.name
    .flatMap (file) ->
      h(fs.createReadStream file.name)
        .split()
        .takeUntil (line) -> not line.startsWith '#' and line isnt ''
        .last(1)
        .map (comment) -> {name: file.name, comment}

让我们来看看这段代码中的类型。首先,请注意flatMap具有类型(使用Haskellish表示法)Stream a → (a → Stream b) → Stream b,即它包含一些包含a类型的东西的流,以及一个期望类型为a的函数的函数并返回包含b s的流,并返回包含b s的流。它是集合类型(例如流和数组)的标准,用于将flatMap实现为连接返回的集合。

h(readdirp root: root, depth: 0, entryType: 'directories')

假设这个类型为Stream Directoryfilter不会更改类型,因此flatMap将为Stream Directory → (Directory → Stream b) → Stream b。我们将看到函数返回的内容:

h(readdirp root: dir.fullPath, depth: 0, entryType: 'files', fileFilter: '!.DS_Store')

将其称为Stream File,因此第二个flatMapStream File → (File → Stream b) → Stream b

h(fs.createReadStream file.name)

这是Stream StringsplittakeUntillast不会改变这一点,那么map会做什么? mapflatMap非常相似:其类型为Stream a → (a → b) → Stream b。在这种情况下,aStringb为对象类型{name : String, comment : String}。然后map返回该对象的流,这是整个flatMap函数返回的内容。升级,第二个b中的flatMap是对象,因此第一个flatMap的函数也返回对象的流,因此整个流是{{1} }。

请注意,由于Highland的懒惰,这实际上并不会启动任何流媒体或处理。您需要使用Stream {name : String, comment : String}each来生成toArray并启动管道。在thunk中,将使用您的对象调用回调。根据您对评论的要求,最好each一些(例如,如果您将它们写入文件)。

好吧,我不是故意写一篇文章。希望这会有所帮助。