UNPKG

funstream

Version:

Funstream gives you iteratorish methods on your streams.

132 lines (122 loc) 4.53 kB
'use strict' module.exports = mixinPromise const is = require('isa-stream') const PROMISE = Symbol('promise') const CLOSEPROMISE = Symbol('closePromise') const MAKEPROMISE = Symbol('makePromise') mixinPromise.PROMISE = PROMISE function mixinPromise (Promise, stream) { const cls = typeof stream === 'function' ? stream : null const obj = cls ? cls.prototype : stream if (MAKEPROMISE in obj) return if (!(PROMISE in obj)) { obj[PROMISE] = null obj[CLOSEPROMISE] = null let error const onError = err => { error = err } obj.once('error', onError) if (is.Writable(stream)) { let result const onEarlyResult = value => { result = value } obj.once('result', onEarlyResult) let finished = false const onEarlyFinish = () => setImmediate(() => { finished = true }) obj.once('finish', onEarlyFinish) obj[MAKEPROMISE] = function () { if (error) { this.removeListener('result', onEarlyResult) this.removeListener('finish', onEarlyFinish) this.removeListener('close', onEarlyClose) this[PROMISE] = Promise.reject(error) } else if (result || finished) { this.removeListener('error', onError) this[PROMISE] = Promise.resolve(result) } else { this[PROMISE] = new Promise((resolve, reject) => { this.removeListener('error', onError) this.removeListener('result', onEarlyResult) this.removeListener('finish', onEarlyFinish) this.once('result', resolve) // make sure finish will always lose any race w/ result this.once('finish', () => setImmediate(resolve)) this.once('error', reject) }) } } let closed = false const onEarlyClose = () => setImmediate(() => { closed = true }) obj.once('close', onEarlyClose) obj.closed = function () { if (!is.Writable(this)) throw new TypeError('This stream is not a writable stream, it will not close. Try `.ended()` instead.') if (this[CLOSEPROMISE]) return this[CLOSEPROMISE] if (error) { this.removeListener('close', onEarlyFinish) return this } if (closed) return this[CLOSEPROMISE] = Promise.resolve() return this[CLOSEPROMISE] = new Promise((resolve, reject) => { this.removeListener('close', onEarlyFinish) this.once('error', reject) this.once('close', resolve) }) } } else { let ended = false let result const onEarlyResult = value => { result = value } obj.once('result', onEarlyResult) const onEarlyEnd = () => { ended = true } obj.once('end', onEarlyEnd) obj[MAKEPROMISE] = function () { if (error) { this.removeListener('result', onEarlyResult) this.removeListener('end', onEarlyEnd) this[PROMISE] = Promise.reject(error) } else if (result || ended) { this.removeListener('error', onError) this[PROMISE] = Promise.resolve() } else { this.removeListener('result', onEarlyResult) this.removeListener('end', onEarlyEnd) this.removeListener('error', onError) this[PROMISE] = new Promise((resolve, reject) => { this.once('result', resolve) this.once('end', () => setImmediate(resolve)) this.once('error', reject) }) } } } } // the fun-stream ways of requesting a promise are passthroughs const nop = function () { return this } if (is.Writable(obj)) { obj.finished = nop } else { obj.ended = nop } // the core interface for (let name of ['then', 'catch']) { let func = obj[PROMISE] ? obj[PROMISE][name] : Promise.prototype[name] obj[name] = makeProxy(func) } // and everything else, iterating prototype doesn't // work on builtin promises, thus the hard coded list above. const methods = obj[PROMISE] ? Object.keys(Object.getPrototypeOf(obj[PROMISE])).concat(Object.keys(obj[PROMISE])) : Object.keys(Promise.prototype) methods.forEach(name => { if (name[0] === '_') return if (name in obj) return let func = Promise.prototype[name] if (typeof func !== 'function') return obj[name] = makeProxy(func) }) return stream } function makeProxy (func) { return function () { if (!this[PROMISE]) this[MAKEPROMISE]() return func.apply(this[PROMISE], arguments) } }