根据rxjs

时间:2017-11-05 10:58:42

标签: javascript node.js asynchronous rxjs

我有一个进程,每隔一段时间发送一次数据包,我需要根据数据包到达的时间等来管理该数据流。在某些时候,我也关闭了流和流程。

现在,我使用一组计时器来做这件事,但我希望我可以用rxjs做到这一点,因为它似乎非常适合这种事情。到目前为止,我还没有取得多大成功。

问题

该流应该定期向我发送数据包,但它通常偏离很多,有时会卡住。

我希望在以下条件下关闭流:

  1. 如果第一个数据包发送给我的时间超过startDelay
  2. 发送第一个数据包后,如果两个数据包之间的暂停时间超过middleDelay
  3. 经过一段时间maxChannelTime
  4. 由于上述任何原因我即将关闭流时,我首先要求它礼貌地关闭以便它可以进行一些清理。有时它还会在清理过程中向我发送最终数据包。但是我希望等待不超过cleanupTime进行清理,并且在关闭流之前到达最后一个数据并忽略更多消息。

    精化

    我将创建"流"通过使用Observable包装事件。我毫不费力地做到了。

    关闭"关闭"一个流,我的意思是告诉流程停止发送数据,并可能关闭(即死亡)。

3 个答案:

答案 0 :(得分:2)

棘手的问题。

我把它分解为两个阶段 - '受监管'(因为我们想要定期检查)和'清理'。

向后工作,输出

const regulated = source.takeUntil(close)
const cleanup = source.skipUntil(close).takeUntil(cleanupCloser)
const output = regulated.merge(cleanup)

'Closers'是在关闭时发出的可观察量(每个超时值更近一个)。

const startTimeout = 600
const intervalTimeout = 200
const maxtimeTimeout = 3000
const cleanupTimeout = 300

const startCloser = Observable.timer(startTimeout)  // emit once after initial delay
  .takeUntil(source)                                // cancel after source emits
  .mapTo('startTimeoutMarker')

const intervalCloser = source.switchMap(x =>    // reset interval after each source emit
    Observable.timer(intervalTimeout)           // emit once after intervalTimeout
      .mapTo('intervalTimeoutMarker')
  )

const maxtimeCloser = Observable.timer(maxtimeTimeout)  // emit once after maxtime
  .takeUntil(startCloser)                               // cancel if startTimeout
  .takeUntil(intervalCloser)                            // cancel if intervalTimeout
  .mapTo('maxtimeTimeoutMarker')

const close = Observable.merge(startCloser, intervalCloser, maxtimeCloser).take(1)

const cleanupCloser = close.switchMap(x =>      // start when close emits
     Observable.timer(cleanupTimeout)           // emit once after cleanup time
  ) 
  .mapTo('cleanupTimeoutMarker')

这是一个工作样本CodePen(请一次运行一个测试)

答案 1 :(得分:1)

如果不知道如何使用RxJS创建“流”或者以后如何使用它们,很难给出任何建议。

一般情况下,只需takeUntil()switchMap()timeout()即可实现您的目标。

Observable.defer(...)
  .startWith(undefined) // Trigger the first `timeout`
  .switchMap((val, i) => {
    if (i === 0) { // waiting for the first value
      return Observable.of().timeout(startDelay);
    } else {
      return Observable.of(val).timeout(middleDelay);
    }
  })
  .takeUntil(Observable.timer(maxChannelTime));

我不知道你的意思是“在某个时候关闭流”。您希望errorcomplete通知吗?超时到期时此解决方案将发出error,如果发出complete,则会takeUntil

答案 2 :(得分:0)

最后,这就是我所做的。我的回答主要基于理查德马特森的回答,所以我将他的答案视为已被接受。

事实证明,我需要进行一些额外的更改。

此代码是接收数据消息流并返回包含所有收集的数据和终止原因的单例observable的代码。

let startCloser$ = Observable.timer(this.options.maxStartDelay).takeUntil(dataStream$).mapTo(TerminationReason.StartTimeout);

let intervalCloser$ = dataStream$.switchMap(x => Observable.timer(this.options.timeBetweenPackets).mapTo(TerminationReason.Inactivity));

let maxTimeCloser$ = Observable.timer(this.options.totalConnectionTime).takeUntil(startCloser$).takeUntil(intervalCloser$).mapTo(TerminationReason.ChannelTimeout);

//we need to publishReplay it so we can get the reason afterwards...
let close$ = startCloser$.merge(intervalCloser$, maxTimeCloser$).take(1).publishReplay(1);
//basically treating close$ like a promise
close$.connect();

//cleanupAction has side-effects so it must only be subscribed to once.
let cleanupAction$ = Observable.defer(async () => {
    //it's just a promise that yields nothing and waits until requestTermination has terminated
    //requestTermination is an async function and it already has a timeout thing in promise-language
    await this.requestTermination();
});

let result$ = dataStream$.takeUntil(close$).concat(dataStream$.takeUntil(cleanupAction$)).toArray().switchMap(arrs => {
    //switchMap will only resolve once because the observable is a singleton

    return close$.map(reason => {
        //this should fire immediately because close is publishReplay(1) and has already happened
        let totalArr = _.flattenDeep(arrs);
        return {
            reason : reason,
            data : totalArr
        }
    })
});

return result$;