Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/462.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 如何交错流(带背压)_Javascript_Rxjs_Bacon.js - Fatal编程技术网

Javascript 如何交错流(带背压)

Javascript 如何交错流(带背压),javascript,rxjs,bacon.js,Javascript,Rxjs,Bacon.js,假设我有两条可能无限的流: s1 = a..b..c..d..e... s2 = 1.2.3.4.5.6.7... 我想合并这些流,然后将合并的流映射到slowish异步操作(例如,在Bacon中使用fromPromise和flatMapConcat) 我可以将它们与合并: me = a12b3.c45d6.7e... 然后是地图 s1 = a..b..c..d..e... s2 = 1.2.3.4.5.6.7... me = a12b3.c45d6.7e... mm = a..1..2..

假设我有两条可能无限的流:

s1 = a..b..c..d..e...
s2 = 1.2.3.4.5.6.7...
我想合并这些流,然后将合并的流映射到slowish异步操作(例如,在Bacon中使用
fromPromise
flatMapConcat

我可以将它们与
合并

me = a12b3.c45d6.7e...
然后是地图

s1 = a..b..c..d..e...
s2 = 1.2.3.4.5.6.7...
me = a12b3.c45d6.7e...
mm = a..1..2..b..3..c..4..5..
正如您所看到的,从长远来看,greedier
s2
streams具有优势这是不受欢迎的行为


合并行为是不好的,因为我想有一些背压,以有更多的交错,“公平”,“循环”合并。期望的行为的几个例子:

s1 = a.....b..............c...
s2 = ..1.2.3..................
mm = a...1...b...2...3....c...

s1 = a.........b..........c...
s2 = ..1.2.3..................
mm = a...1...2...b...3....c...
一种想法是
s1
s2
将任务发送给工人,而工人一次只能处理一项任务。使用
merge
flatMapConcat
我会得到一个贪婪的任务管理器,但我想要一个更公平的


我想找到一个简单而优雅的解决方案。如果它很容易推广到任意数量的流,那就太好了:

// roundRobinPromiseMap(streams: [Stream a], f: a -> Promise b): Stream b
var mm = roundRobinPromiseMap([s1, s2], slowAsyncFunc);
使用RxJS或其他Rx库的解决方案也不错


澄清 不是zipAsArray 我不想:

function roundRobinPromiseMap(streams, f) {
  return Bacon.zipAsArray.apply(null, streams)
    .flatMap(Bacon.fromArray)
    .flatMapConcat(function (x) {
      return Bacon.fromPromise(f(x));
    });
}
比较示例大理石图:

s1  = a.....b..............c.......
s2  = ..1.2.3......................
mm  = a...1...b...2...3....c....... // wanted
zip = a...1...b...2........c...3... // zipAsArray based
是的,我会遇到缓冲问题 。。。但我也会这么说:

大理石图

s1    = a.........b..........c...
s2    = ..1.2.3..................
mm    = a...1...2...b...3....c...
merge = a...1...2...3...b....c...

这里有一段疯狂的代码可能会有所帮助

它将输入流转换为单个“值”事件流,然后将它们与“发送”事件(以及用于记账的“结束”事件)合并。然后,使用状态机,它在“值”事件之外建立队列,并在“发送”事件上分派值

起初我写了一篇文章,但我把它改成了一篇要点

这是一张非常相似的圆形RobinPromiseMap。gist中的代码已经过测试,但这不是

# 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
#roundRobinPromiseMap::(a->Promise b)->[EventStream]->EventStream
roundRobinPromiseMap=(promiser,streams)->
#基于承诺履行触发新发送的总线
PromiseCompleted=新培根
#将输入流合并为单个键控流
theStream=Bacon.mergeAll(streams.map((s,idx)->
s、 地图((val)->{
类型:“值”
索引:idx
值:val
})
))
#在“结束”事件中合并
.merge(Bacon.mergeAll(streams.map)(s)->
s、 mapEnd(->{
键入:“结束”
})
)))
#合并在“发送”事件中,该事件在承诺履行时触发。
.merge(promisemplied.map({type:'send'}))
#馈送到一个状态机,该状态机保持队列并只创建
#“发送”输入事件上的输出事件。
.带状态机(
{
队列:streams.map(->[])
toPush:0
完:0
}
扶手
)
#将此输出馈送到promiser
theStream.onValue((value)->
培根.fromPromise(promiser(value)).onValue(->
履行诺言
))
handleState=(州,baconEvent)->
outEvents=[]
如果baconEvent.hasValue()
#处理“value”、“send”或“end”的循环事件
outEvents=handleRoundRobinEvent(状态,baconEvent.value())
其他的
outEvents=[baconEvent]
[州,州]
handleRoundRobinEvent=(状态,rrEvent)->
outEvents=[]
#“值”:推送到队列上
如果rrEvent.type=='value'
state.queues[rrEvent.index].push(rrEvent.value)
#“发送”:通过循环选择发送下一个值
如果rrEvent.type=='send'
#这里有一个空队哨兵
noValue={}
下一个值=新值
triedQueues=0
而nextValue==noValue&&triedQueues0
nextValue=state.queues[state.toPush].shift()
state.toPush=(state.toPush+1)%state.queues.length
搜索队列++
如果下一个值!=诺瓦卢
outEvents.push(新培根,下一个(下一个值))
#“结束”:跟踪结束的流
如果rEvent.type=='end',则为else
结束++
#如果所有输入都已结束,则结束循环流
如果为圆形(州)
outEvents.push(new Bacon.End())
外衣
RoundRobineded=(州)->
emptyQueues=allEmpty(state.queues)
emptyQueues&&state.ended==state.queues.length
allEmpty=(数组)->
对于数组中的
如果a.length>0,则返回false
返回真值

这里的核心挑战是理解如何将公平正式化。在问题中,我已经提到了工人类比。事实证明,明显的公平性标准是选择一个比其他流生成的事件更少的流,或者更进一步:谁生成的流等待的时间更少

在此之后,使用指称语义将所需输出形式化是非常简单的:

我没有时间开发从Bacon.js中包含
和stateMachine
的指称组合词,因此下一步是直接用Bacon.js在JavaScript中重新实现它。整个可运行的解决方案是

这个想法是用

  • 每流成本和队列状态
  • 流和附加反馈流作为输入
当整个系统的输出被反馈时,我们可以在前一个平面映射流结束时将下一个事件出列

为此,我不得不制作一个有点难看的
rec
combinator

function rec(f) {
  var bus = new Bacon.Bus();
  var result = f(bus);
  bus.plug(result);
  return result;
}
它的类型是
(EventStream a->EventStream a)->EventStream a
-该类型类似于其他递归组合符,例如
fix

当总线中断取消订阅传播时,它可以具有更好的系统范围行为。我们必须在这方面努力

第二个辅助函数是
stateMachine
,它接受一组流并将它们转换为单个状态机。本质上,它是
。带状态机的∘ 合并所有∘ zipWithIndex

function stateMachine(inputs, initState, f) {
  var mapped = inputs.map(function (input, i) {
    return input.map(function (x) {
      return [i, x];
    })
  });
  return Bacon.mergeAll(mapped).withStateMachine(initState, function (state, p) {
    if (p.hasValue()) {
      p = p.value();
      return f(state, p[0], p[1]);
    } else {
      return [state, p];
    }
  });
}
使用这两个助手
function stateMachine(inputs, initState, f) {
  var mapped = inputs.map(function (input, i) {
    return input.map(function (x) {
      return [i, x];
    })
  });
  return Bacon.mergeAll(mapped).withStateMachine(initState, function (state, p) {
    if (p.hasValue()) {
      p = p.value();
      return f(state, p[0], p[1]);
    } else {
      return [state, p];
    }
  });
}
function fairScheduler(streams, fn) {
  var streamsCount = streams.length;
  return rec(function (res) {
    return stateMachine(append(streams, res), initialFairState(streamsCount), function (state, i, x) {
      // console.log("FAIR: " + JSON.stringify(state), i, x);

      // END event
      if (i == streamsCount && x.end) {
        var additionalCost = new Date().getTime() - x.started;

        // add cost to input stream cost center
        var updatedState = _.extend({}, state, {
          costs: updateArray(
            state.costs,
            x.idx, function (cost) { return cost + additionalCost; }),
        });

        if (state.queues.every(function (q) { return q.length === 0; })) {
          // if queues are empty, set running: false and don't emit any events
          return [_.extend({}, updatedState, { running: false }), []];
        } else {
          // otherwise pick a stream with
          // - non-empty queue
          // - minimal cost
          var minQueueIdx = _.chain(state.queues)
            .map(function (q, i) {
              return [q, i];
            })
            .filter(function (p) {
              return p[0].length !== 0;
            })
            .sortBy(function (p) {
              return state.costs[p[1]];
            })
            .value()[0][1];

          // emit an event from that stream
          return [
            _.extend({}, updatedState, {
              queues: updateArray(state.queues, minQueueIdx, function (q) { return q.slice(1); }),
              running: true,
            }),
            [new Bacon.Next({
              value: state.queues[minQueueIdx][0],
              idx: minQueueIdx,
            })],
          ];
        }
      } else if (i < streamsCount) {
        // event from input stream
        if (state.running) {
          // if worker is running, just enquee the event
          return [
            _.extend({}, state, {
              queues: updateArray(state.queues, i, function (q) { return q .concat([x]); }),
            }),
            [],
          ];
        } else {
          // if worker isn't running, start it right away
          return [
            _.extend({}, state, {
              running: true,
            }),
            [new Bacon.Next({ value: x, idx: i})],
          ]
        }
      } else {
        return [state, []];
      }

    })
    .flatMapConcat(function (x) {
      // map passed thru events,
      // and append special "end" event
      return fn(x).concat(Bacon.once({
        end: true,
        idx: x.idx,
        started: new Date().getTime(),
      }));
    });
  })
  .filter(function (x) {
    // filter out END events
    return !x.end;
  })
  .map(".value"); // and return only value field
}