是否有使用 ES6 生成器使流可迭代的模式?
不,这无法实现,因为生成器是同步的。他们必须知道他们在什么时候放弃什么。异步数据源的迭代目前只能通过使用某种基于回调的实现来实现。因此,如果您的意思是“可以将结果提供给for-of 循环的有效函数”,则无法使MakeStreamIterable 成为“有效函数”。
流是异步的
流表示在可能无限的时间内异步接收的可能无限量的数据。如果我们看一下definition of an iterator on MDN,我们可以更详细地定义它是什么关于使其“可单元化”的流:
当一个对象知道如何一次访问一个集合中的项目,同时跟踪它在该序列中的当前位置时,它就是一个迭代器。在 JavaScript 中,迭代器是一个对象,它提供了一个 next() 方法,该方法返回序列中的下一项。此方法返回一个具有两个属性的对象:done 和 value。
(重点是我自己的。)
让我们从这个定义中挑选出一个可迭代对象的属性。对象必须...
- 知道如何一次访问一个集合中的项目;
- 能够跟踪其在数据序列中的当前位置;
- 并提供一个方法
next,该方法检索具有属性的对象,该属性包含序列中的下一个value,或通知迭代为done。
流不符合上述任何条件,因为...
- 它无法控制何时它接收到数据并且不能“展望未来”来寻找下一个值;
- 它无法知道何时或是否收到了所有数据,只有当流关闭时;
- 并且它没有实现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 .
- 我培养了将其称为迭代器的错误语义,因此我是一个糟糕的人。请向相关部门举报。