【问题标题】:Javascript (ES6) iterable streamJavascript (ES6) 可迭代流
【发布时间】:2015-12-30 08:48:37
【问题描述】:

是否有使用 ES6 生成器使流可迭代的模式?

请参阅下面的“MakeStreamIterable”。

import {createReadStream} from 'fs'

let fileName = 'largeFile.txt'
let readStream = createReadStream(fileName, {
  encoding: 'utf8',
  bufferSize: 1024
})
let myIterableAsyncStream = MakeStreamIterable(readStream)

for (let data of myIterableAsyncStream) {
  let str = data.toString('utf8')
  console.log(str)
}

我对 co 或 bluebird 的 coroutine 或使用 deasync 阻塞不感兴趣。

金是MakeStreamIterable应该是一个有效的函数。

【问题讨论】:

  • 你怎么定义它(data...)?一个字节一个字节(这不是它的样子)?
  • 数据来自 readStream.on('data', data => ...)
  • 我想不出解决办法。在我看来,这似乎是不可能的。
  • 我认为没有 co 之类的东西你就做不到。你为什么反对?
  • for...of 是同步的。您不能对异步数据源进行同步迭代。有一个异步迭代器的提议,但它被 github.com/zenparsing/es-observable 放弃了。

标签: javascript stream iterator ecmascript-6


【解决方案1】:

是否有使用 ES6 生成器使流可迭代的模式?

不,这无法实现,因为生成器是同步的。他们必须知道他们在什么时候放弃什么。异步数据源的迭代目前只能通过使用某种基于回调的实现来实现。因此,如果您的意思是“可以将结果提供给for-of 循环的有效函数”,则无法使MakeStreamIterable 成为“有效函数”。

流是异步的

流表示在可能无限的时间内异步接收的可能无限量的数据。如果我们看一下definition of an iterator on MDN,我们可以更详细地定义它是什么关于使其“可单元化”的流:

当一个对象知道如何一次访问一个集合中的项目,同时跟踪它在该序列中的当前位置时,它就是一个迭代器。在 JavaScript 中,迭代器是一个对象,它提供了一个 next() 方法,该方法返回序列中的下一项。此方法返回一个具有两个属性的对象:donevalue

(重点是我自己的。)

让我们从这个定义中挑选出一个可迭代对象的属性。对象必须...

  1. 知道如何一次访问一个集合中的项目;
  2. 能够跟踪其在数据序列中的当前位置;
  3. 并提供一个方法next,该方法检索具有属性的对象,该属性包含序列中的下一个value,或通知迭代为done

流不符合上述任何条件,因为...

  1. 它无法控制何时它接收到数据并且不能“展望未来”来寻找下一个值;
  2. 它无法知道何时或是否收到了所有数据,只有当流关闭时;
  3. 并且它没有实现iterable protocol,因此没有公开for-of 可以使用的next 方法。

______

假装它(迭代)

我们不能实际上迭代从流中接收的数据(绝对不使用for-of),但是我们可以构建一个假装的接口,使用承诺(耶!)并在闭包中抽象出流的事件处理程序。

// MakeStreamIterable.js
export default function MakeStreamIterable (stream) {
  let collection = []
  let index = 0
  let callback
  let resolve, reject

  stream
    .on('error', err => reject && reject(err))
    .on('end', () => resolve && resolve(collection))
    .on('data', data => {
      collection.push(data)

      try {
        callback && callback(data, index++)
      } catch (err) {
        this.end()
        reject(err)
      }
    })

  function each (cb) {
    if(callback) {
      return promise
    }

    callback = (typeof cb === 'function') ? cb : null

    if (callback && !!collection) {
        collection.forEach(callback)
        index = collection.length
    }

    return promise
  }

  promise = new Promise((res, rej) => {
    resolve = res
    reject = rej
  })

  promise.each = each

  return promise
}

我们可以这样使用它:

import {MakeStreamIterable} from './MakeStreamIterable'

let myIterableAsyncStream = MakeStreamIterable(readStream)

myIterableAsyncStream
  .each((data, i) => {
    let str = data.toString('utf8')
    console.log(i, str)
  })
  .then(() => console.log('completed'))
  .catch(err => console.log(err))

关于此实现的注意事项:

  • 不必立即在“可迭代流”上调用each
  • each 被调用时,所有在调用之前接收到的值都会被一一传递给回调forEach 样式。之后所有后续数据都会立即传递给回调。
  • 该函数返回一个 Promise,它在流结束时解析完整的数据collection,这意味着如果each 提供的迭代方法不令人满意,我们实际上根本不需要调用each .
  • 我培养了将其称为迭代器的错误语义,因此我是一个糟糕的人。请向相关部门举报。

【讨论】:

  • 不错的抽象。像这样的东西应该是内置的。
【解决方案2】:

很快您就可以使用Async Iterators and Generators。在节点 9.8 中,您可以通过运行 --harmony 命令行选项来使用它。

async function* streamAsyncIterator(stream) {
  // Get a lock on the stream
  const reader = stream.getReader();

  try {
    while (true) {
      // Read from the stream
      const {done, value} = await reader.read();
      // Exit if we're done
      if (done) return;
      // Else yield the chunk
      yield value;
    }
  }
  finally {
    reader.releaseLock();
  }
}

async function example() {
  const response = await fetch(url);

  for await (const chunk of streamAsyncIterator(response.body)) {
    // …
  }
}

感谢 Jake Archibald 提供上述examples

【讨论】:

    【解决方案3】:

    【讨论】:

      猜你喜欢
      • 2015-06-01
      • 2020-04-14
      • 2017-07-06
      • 2019-07-02
      • 2015-12-24
      • 2021-04-21
      • 2017-10-22
      • 1970-01-01
      • 2023-03-11
      相关资源
      最近更新 更多