UNPKG

kefir

Version:

Reactive Programming library for JavaScript inspired by Bacon.js and RxJS with focus on high performance and low memory usage

198 lines (170 loc) 5.26 kB
import Stream from '../stream' import {VALUE, ERROR, NOTHING} from '../constants' import {inherit} from '../utils/objects' import {concat, fillArray} from '../utils/collections' import {spread} from '../utils/functions' import never from '../primary/never' function collect(source, keys, values) { for (var prop in source) { if (source.hasOwnProperty(prop)) { keys.push(prop) values.push(source[prop]) } } } function defaultErrorsCombinator(errors) { let latestError for (let i = 0; i < errors.length; i++) { if (errors[i] !== undefined) { if (latestError === undefined || latestError.index < errors[i].index) { latestError = errors[i] } } } return latestError.error } function Combine(active, passive, combinator) { Stream.call(this) this._activeCount = active.length this._sources = concat(active, passive) this._combinator = combinator this._aliveCount = 0 this._latestValues = new Array(this._sources.length) this._latestErrors = new Array(this._sources.length) fillArray(this._latestValues, NOTHING) this._emitAfterActivation = false this._endAfterActivation = false this._latestErrorIndex = 0 this._$handlers = [] for (let i = 0; i < this._sources.length; i++) { this._$handlers.push(event => this._handleAny(i, event)) } } inherit(Combine, Stream, { _name: 'combine', _onActivation() { this._aliveCount = this._activeCount // we need to suscribe to _passive_ sources before _active_ // (see https://github.com/kefirjs/kefir/issues/98) for (let i = this._activeCount; i < this._sources.length; i++) { this._sources[i].onAny(this._$handlers[i]) } for (let i = 0; i < this._activeCount; i++) { this._sources[i].onAny(this._$handlers[i]) } if (this._emitAfterActivation) { this._emitAfterActivation = false this._emitIfFull() } if (this._endAfterActivation) { this._emitEnd() } }, _onDeactivation() { let length = this._sources.length, i for (i = 0; i < length; i++) { this._sources[i].offAny(this._$handlers[i]) } }, _emitIfFull() { let hasAllValues = true let hasErrors = false let length = this._latestValues.length let valuesCopy = new Array(length) let errorsCopy = new Array(length) for (let i = 0; i < length; i++) { valuesCopy[i] = this._latestValues[i] errorsCopy[i] = this._latestErrors[i] if (valuesCopy[i] === NOTHING) { hasAllValues = false } if (errorsCopy[i] !== undefined) { hasErrors = true } } if (hasAllValues) { const combinator = this._combinator this._emitValue(combinator(valuesCopy)) } if (hasErrors) { this._emitError(defaultErrorsCombinator(errorsCopy)) } }, _handleAny(i, event) { if (event.type === VALUE || event.type === ERROR) { if (event.type === VALUE) { this._latestValues[i] = event.value this._latestErrors[i] = undefined } if (event.type === ERROR) { this._latestValues[i] = NOTHING this._latestErrors[i] = { index: this._latestErrorIndex++, error: event.value, } } if (i < this._activeCount) { if (this._activating) { this._emitAfterActivation = true } else { this._emitIfFull() } } } else { // END if (i < this._activeCount) { this._aliveCount-- if (this._aliveCount === 0) { if (this._activating) { this._endAfterActivation = true } else { this._emitEnd() } } } } }, _clear() { Stream.prototype._clear.call(this) this._sources = null this._latestValues = null this._latestErrors = null this._combinator = null this._$handlers = null }, }) function combineAsArray(active, passive = [], combinator) { if (!Array.isArray(passive)) { throw new Error('Combine can only combine active and passive collections of the same type.') } combinator = combinator ? spread(combinator, active.length + passive.length) : x => x return active.length === 0 ? never() : new Combine(active, passive, combinator) } function combineAsObject(active, passive = {}, combinator) { if (typeof passive !== 'object' || Array.isArray(passive)) { throw new Error('Combine can only combine active and passive collections of the same type.') } let keys = [], activeObservables = [], passiveObservables = [] collect(active, keys, activeObservables) collect(passive, keys, passiveObservables) const objectify = values => { let event = {} for (let i = values.length - 1; 0 <= i; i--) { event[keys[i]] = values[i] } return combinator ? combinator(event) : event } return activeObservables.length === 0 ? never() : new Combine(activeObservables, passiveObservables, objectify) } export default function combine(active, passive, combinator) { if (typeof passive === 'function') { combinator = passive passive = undefined } return Array.isArray(active) ? combineAsArray(active, passive, combinator) : combineAsObject(active, passive, combinator) }