funstream
Version:
Funstream gives you iteratorish methods on your streams.
209 lines (198 loc) • 7.32 kB
JavaScript
'use strict'
const fun = require('./index.js')
const mixinPromiseStream = require('./mixin-promise-stream.js')
const is = require('isa-stream')
let FilterStream
let MapStream
let FlatMapStream
let ReduceStream
let ForEachStream
const OPTS = Symbol('opts')
const ISFUN = Symbol('isFun')
const PROMISES = Symbol('promises')
const RESULT = Symbol('result')
class FunStream {
init (opts) {
this[OPTS] = Object.assign({Promise: Promise}, opts || {})
this[ISFUN] = true
this[PROMISES] = {}
this[RESULT] = null
}
ended () {
if (!is.Readable(this)) throw new TypeError('This stream is not a readable stream, it will not end. Try `.finished()` instead.')
if (this[PROMISES].ended) return this[PROMISES].ended
return this[PROMISES].ended = new this[OPTS].Promise((resolve, reject) => {
this.once('error', reject)
this.once('end', () => setImmediate(resolve, this[RESULT]))
})
}
finished () {
if (!is.Writable(this)) throw new TypeError('This stream is not a writable stream, it will not finish. Try `.ended()` instead.')
if (this[PROMISES].finished) return this[PROMISES].finished
return this[PROMISES].finished = new this[OPTS].Promise((resolve, reject) => {
this.once('error', reject)
this.once('finish', () => setImmediate(resolve, this[RESULT]))
})
}
closed () {
if (!is.Writable(this)) throw new TypeError('This stream is not a writable stream, it will not close. Try `.ended()` instead.')
if (this[PROMISES].closed) return this[PROMISES].closed
return this[PROMISES].closed = new this[OPTS].Promise((resolve, reject) => {
this.once('error', reject)
this.once('close', resolve)
})
}
async (todo) {
if (todo) {
const value = this[OPTS].async
this[OPTS].async = true
const next = todo.call(this, this)
next[OPTS].async = value
return next
} else {
this[OPTS].async = true
return this
}
}
sync (todo) {
if (todo) {
const value = this[OPTS].async
this[OPTS].async = false
const next = todo.call(this, this)
next[OPTS].async = value
return next
} else {
this[OPTS].async = false
return this
}
}
filter (filterWith, opts) {
if (!FilterStream) FilterStream = require('./filter-stream.js')
const filter = FilterStream(filterWith, opts ? Object.assign(this[OPTS], opts) : this[OPTS])
return this.pipe(filter)
}
map (mapWith, opts) {
if (!MapStream) MapStream = require('./map-stream.js')
const map = MapStream(mapWith, opts ? Object.assign(this[OPTS], opts) : this[OPTS])
return this.pipe(map)
}
flat (opts) {
return this.sync(o => o.flatMap(v => v, opts))
}
flatMap (mapWith, opts) {
if (!FlatMapStream) FlatMapStream = require('./flat-map-stream.js')
const map = FlatMapStream(mapWith, opts ? Object.assign(this[OPTS], opts) : this[OPTS])
return this.pipe(map)
}
head (maxoutput) {
let seen = 0
return this.sync(o => o.filter(() => seen++ < maxoutput))
}
reduce (reduceWith, initial, reduceOpts) {
if (!ReduceStream) ReduceStream = require('./reduce-stream.js')
const opts = Object.assign({}, this[OPTS], reduceOpts || {})
return this.pipe(ReduceStream(reduceWith, initial, opts))
}
reduceTo (reduceWith, initial, reduceOpts) {
const opts = Object.assign({}, this[OPTS], reduceOpts || {})
let reduceToWith
if (isAsync(reduceWith, 2, opts)) {
reduceToWith = (acc, value, cb) => {
return new opts.Promise((resolve, reject) => {
const result = reduceWith(acc, value, err => err ? reject(err) : resolve(acc))
if (result && result.then) resolve(acc)
})
}
} else {
/* eslint no-sequences:0 */
reduceToWith = (acc, value) => (reduceWith(acc, value), acc)
}
return this.reduce(reduceToWith, initial, opts)
}
reduceToObject (reduceWith, opts) {
return this.reduceTo(reduceWith, {}, opts)
}
reduceToArray (reduceWith, opts) {
return this.reduceTo(reduceWith, [], opts)
}
list (opts) {
return this.sync(o => o.reduceToArray((acc, val) => acc.push(val), opts))
}
grab (whenDone, opts) {
return fun(this.list().then(v => whenDone(v)))
}
sort (sortWith, opts) {
return this.grab(v => v.sort(sortWith))
}
concat (opts) {
return this.sync(o => o.reduce((acc, val) => acc + String(val), '', opts))
}
forEach (forEachWith, forEachOpts) {
if (!ForEachStream) ForEachStream = require('./for-each-stream.js')
const opts = Object.assign({}, this[OPTS], forEachOpts || {})
return this.pipe(ForEachStream(forEachWith, opts))
}
}
FunStream.isFun = stream => Boolean(stream && stream[ISFUN])
FunStream.mixin = mixinFun
FunStream.isAsync = isAsync
FunStream.funInit = function () {
const fn = this.init ? this.init
: this.prototype && this.prototype.init ? this.prototype.init
: FunStream.prototype.init
return fn.apply(this, arguments)
}
FunStream.OPTS = OPTS
function isAsync (fun, args, opts) {
if (fun.constructor.name === 'AsyncFunction') return true
if (opts && opts.async != null) return opts.async
return fun.length > args
}
function mixinFun (stream, opts) {
if (FunStream.isFun(stream)) return stream
const P = (opts && opts.Promise) || fun.Promise
const cls = typeof stream === 'function' ? stream : null
!cls && mixinPromiseStream(P, stream)
const obj = cls ? cls.prototype : stream
if (cls) {
cls.isFun = FunStream.isFun
cls.mixin = FunStream.mixin
cls.isAsync = FunStream.isAsync
cls.funInit = FunStream.funInit
} else {
FunStream.funInit.call(obj, opts)
}
if (is.Writable(obj)) {
if (!cls || !obj.finished) obj.finished = FunStream.prototype.finished
if (!cls || !obj.closed) obj.closed = FunStream.prototype.closed
}
if (is.Readable(obj)) {
if (!cls || !obj.ended) obj.ended = FunStream.prototype.ended
}
if (!cls || !obj.filter) obj.filter = FunStream.prototype.filter
if (!cls || !obj.map) obj.map = FunStream.prototype.map
if (!cls || !obj.flat) obj.flat = FunStream.prototype.flat
if (!cls || !obj.flatMap) obj.flatMap = FunStream.prototype.flatMap
if (!cls || !obj.head) obj.head = FunStream.prototype.head
if (!cls || !obj.reduce) obj.reduce = FunStream.prototype.reduce
if (!cls || !obj.reduceTo) obj.reduceTo = FunStream.prototype.reduceTo
if (!cls || !obj.reduceToArray) obj.reduceToArray = FunStream.prototype.reduceToArray
if (!cls || !obj.reduceToObject) obj.reduceToObject = FunStream.prototype.reduceToObject
if (!cls || !obj.concat) obj.concat = FunStream.prototype.concat
if (!cls || !obj.list) obj.list = FunStream.prototype.list
if (!cls || !obj.grab) obj.grab = FunStream.prototype.grab
if (!cls || !obj.sort) obj.sort = FunStream.prototype.sort
if (!cls || !obj.forEach) obj.forEach = FunStream.prototype.forEach
if (!cls || !obj.sync) obj.sync = FunStream.prototype.sync
if (!cls || !obj.async) obj.async = FunStream.prototype.async
const originalPipe = obj.pipe
obj.pipe = function (into, opts) {
this.on('error', err => {
if (err.src === undefined) err.src = this
into.emit('error', err)
})
return fun(originalPipe.call(this, into, opts), this[OPTS])
}
return obj
}
module.exports = FunStream