How to interleave streams (with backpressure)

前端 未结 2 615
悲哀的现实
悲哀的现实 2021-01-02 03:18

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

2条回答
  •  迷失自我
    2021-01-02 04:01

    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
    

提交回复
热议问题