Suppose I have two possibly infinite streams:
s1 = a..b..c..d..e...
s2 = 1.2.3.4.5.6.7...
I want to merge the streams and then map
Here's a crazy chunk of code that might help.
It turns the input streams into a single stream of 'value' events, then merges them with 'send' events (and 'end' events for bookkeeping). Then, using a state machine, it builds up queues out of the 'value' events, and dispatches values on 'send' events.
Originally I wrote a roundRobinThrottle, but I've moved it to a gist.
Here is a roundRobinPromiseMap that is very similar. The code in the gist is tested, but this is not.
# roundRobinPromiseMap :: (a -> Promise b) -> [EventStream] -> EventStream
roundRobinPromiseMap = (promiser, streams) ->
# A bus to trigger new sends based on promise fulfillment
promiseFulfilled = new Bacon.Bus()
# Merge the input streams into a single, keyed stream
theStream = Bacon.mergeAll(streams.map((s, idx) ->
s.map((val) -> {
type: 'value'
index: idx
value: val
})
))
# Merge in 'end' events
.merge(Bacon.mergeAll(streams.map((s) ->
s.mapEnd(-> {
type: 'end'
})
)))
# Merge in 'send' events that fire when the promise is fulfilled.
.merge(promiseFulfilled.map({ type: 'send' }))
# Feed into a state machine that keeps queues and only creates
# output events on 'send' input events.
.withStateMachine(
{
queues: streams.map(-> [])
toPush: 0
ended: 0
}
handleState
)
# Feed this output to the promiser
theStream.onValue((value) ->
Bacon.fromPromise(promiser(value)).onValue(->
promiseFulfilled.push()
))
handleState = (state, baconEvent) ->
outEvents = []
if baconEvent.hasValue()
# Handle a round robin event of 'value', 'send', or 'end'
outEvents = handleRoundRobinEvent(state, baconEvent.value())
else
outEvents = [baconEvent]
[state, outEvents]
handleRoundRobinEvent = (state, rrEvent) ->
outEvents = []
# 'value' : push onto queue
if rrEvent.type == 'value'
state.queues[rrEvent.index].push(rrEvent.value)
# 'send' : send the next value by round-robin selection
else if rrEvent.type == 'send'
# Here's a sentinel for empty queues
noValue = {}
nextValue = noValue
triedQueues = 0
while nextValue == noValue && triedQueues < state.queues.length
if state.queues[state.toPush].length > 0
nextValue = state.queues[state.toPush].shift()
state.toPush = (state.toPush + 1) % state.queues.length
triedQueues++
if nextValue != noValue
outEvents.push(new Bacon.Next(nextValue))
# 'end': Keep track of ended streams
else if rrEvent.type == 'end'
state.ended++
# End the round-robin stream if all inputs have ended
if roundRobinEnded(state)
outEvents.push(new Bacon.End())
outEvents
roundRobinEnded = (state) ->
emptyQueues = allEmpty(state.queues)
emptyQueues && state.ended == state.queues.length
allEmpty = (arrays) ->
for a in arrays
return false if a.length > 0
return true