Javascript(ES6)可迭代流

时间:2015-10-02 07:09:36

标签: javascript stream iterator ecmascript-6

是否存在使用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 <​​/ em>或bluebird的 coroutine 不感兴趣,或者对 deasync 进行阻止。

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

3 个答案:

答案 0 :(得分:7)

  

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

不,这是无法实现的,因为发电机是同步的。他们必须知道他们正在产生什么以及何时产生。目前只能通过使用某种基于回调的实现来实现异步数据源的迭代。因此,无法使MakeStreamIterable&#39;成为有效的功能&#39;如果您的意思是“有效的函数,其结果可以提供给for-of循环”。

Streams is Asynchronous

流表示在可能无限长的时间内异步接收的潜在无限量数据。如果我们看一下definition of an iterator on MDN,我们就可以更详细地定义一条流,让它变得难以理解&#39;:

  

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

(强调是我自己的。)

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

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

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

      假装(eration)

      我们无法实际迭代从流中接收的数据(绝对不使用for-of),但我们可以构建一个假装通过使用Promises(yay!)并在闭包内抽象出流的事件处理程序。

      // 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 {myIterableAsyncStream} 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 {1}}并不令人满意。
      • 我培养了将这称为迭代器的错误语义,因此是一个可怕的人类。请向有关当局报告。

答案 1 :(得分:1)

很快你就可以使用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

答案 2 :(得分:0)