rxjs定期轮询具有可变响应时间

时间:2018-01-11 17:26:07

标签: rxjs observable

我希望轮询端点的速度不会超过每秒一次,并且不会慢于轮询端点所需的时间。不应该有多个未完成的请求。

我想要一种反应式编程方式,每秒至少轮询一次端点,但如果端点花费的时间超过1秒,则下一个请求会立即触发。

在下面的大理石图中,第2次和第3次请求的时间超过1秒,但第4次和第5次请求更快完成。下一个请求在1秒边界上触发,或者在从最后一个未完成请求中获取数据后立即触发。

s---s---s---s---s---s---| # 1 second interval observable
r---r----r--------r-r---| # endpoint begin polling events
-d-------d--------dd-d--| # endpoint data response events

我正试图在大理石图中弄清楚术语,所以我是 假设端点请求的开头应该是 marble我标记为“r”,标记为“d”的大理石事件具有端点数据。

这是我用普通的js做了多少代码。但是,如上所述,随后的请求不会立即触发。

    var poll;
    var previousData;
    var isPolling = false;
    var dashboardUrl = 'gui/metrics/dashboard';
    var intervalMs = updateServiceConfig.getIntervalInMilliSecondForCharts();

    return {
        startInterval: startInterval,
        stopInterval: stopInterval
    };

    function startInterval() {
        stopInterval();
        tryPolling(); // immediately hit the dashboard
        // attempt polling at the interval
        poll = $interval(tryPolling, intervalMs);
    }

    /**
     * attempt polling as long as there is no in-flight request
     * once the in-flight request completes or fails, allow the next request to be processed
     */
    function tryPolling() {
        if (!isPolling) {
            isPolling = true;

            getDashboard()
            // if the dashboard either returns successful or fails, reset the polling boolean
                .then(resetPolling, resetPolling);
        }
    }

    /** there's no longer an in-flight request, so reset the polling boolean */
    function resetPolling() {
        isPolling = false;
    }

    function stopInterval() {
        if (poll) {
            $interval.cancel(poll);
            poll = undefined;
        }
    }

    function getDashboard() {
        return restfulService.get(dashboardUrl)
            .then(updateDashboard);
    }

    function updateDashboard(data) {
        if (!utils.deepEqual(data, previousData)) {
            previousData = angular.copy(data);
            $rootScope.$broadcast('$dashboardLoaded', data);
        }
    }

3 个答案:

答案 0 :(得分:4)

这是我的解决方案。它使用内部主题combineLatestfilter来确保如果回复的响应速度低于timer期,则请求不会累积。

评论应该解释它是如何运作的。



const delays = [100, 2000, 100, 3000];
const since = Date.now();
let index = 0;

function mock() {
    return Rx.Observable
    .of("res")
    .do(() => console.log("mock req at ", Date.now() - since, " ms"))
    .delay(delays[index++ % delays.length])
    .do(() => console.log("mock res at ", Date.now() - since, " ms"));
}

function poll() {

  return Rx.Observable.defer(() => {

    // Use defer so that the internal subject is created for each
    // subscription.
    const subject = new Rx.BehaviorSubject({ tick: -1, pending: false });

    return Rx.Observable
    
      // Combine the timer and the subject's state.
      .combineLatest(
        Rx.Observable.timer(0, 1000).do(tick => console.log("tick", tick)),
        subject
      )

      // Filter out combinations in which either a more recent tick
      // has not occurred or a request is pending.
      .filter(([tick, state]) => (tick !== state.tick) && !state.pending)

      // Update the subject's state.
      .do(([tick]) => subject.next({ tick, pending: true }))
      
      // Make the request and use the result selector to combine
      // the tick and the response.
      .mergeMap(([tick]) => mock(), ([tick], resp) => [tick, resp])

      // Update the subject's state.
      .do(([tick]) => subject.next({ tick, pending: false }))
      
      // Map the response.
      .map(([tick, resp]) => resp);
  });
}

poll().take(delays.length).subscribe(r => console.log(r));

.as-console-wrapper { max-height: 100% !important; top: 0; }

<script src="https://unpkg.com/rxjs@5/bundles/Rx.min.js"></script>
&#13;
&#13;
&#13;

我刚刚想到有一个运营商正是这样做的:exhaustMap

&#13;
&#13;
const delays = [100, 2000, 100, 3000];
const since = Date.now();
let index = 0;

function mock() {
  return Rx.Observable
    .of("res")
    .do(() => console.log("mock req at ", Date.now() - since, " ms"))
    .delay(delays[index++ % delays.length])
    .do(() => console.log("mock res at ", Date.now() - since, " ms"));
}

const poll = Rx.Observable
  .timer(0, 1000)
  .do(tick => console.log("tick", tick))
  .exhaustMap(() => mock());

poll.take(delays.length).subscribe(r => console.log(r));
&#13;
.as-console-wrapper { max-height: 100% !important; top: 0; }
&#13;
<script src="https://unpkg.com/rxjs@5/bundles/Rx.min.js"></script>
&#13;
&#13;
&#13;

答案 1 :(得分:3)

我相信这可以做你想要的:

&#13;
&#13;
let counter = 0;
function apiCall() {
  const delay = Math.random() * 1000;
  const count = ++counter;
  return Rx.Observable.timer(delay).mapTo(count);
}

Rx.Observable.timer(0, 1000)
  .mergeMap(() => apiCall())
  .take(1)
  .repeat()
  .subscribe(x => { console.log(x); });
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
&#13;
&#13;
&#13;

  • timer(0, 1000):立即发出,然后在一秒之后发出
  • mergeMap(...):切换到api调用返回的observable。这将在每次重试时生成一个新的observable。如果您不想在每次重试时创建新的,请将其替换为mergeMapTo(apiCall())
  • take(1):强制订阅完成,以便在api发出后计时器不会触发
  • repeat():在api发出
  • 时启动序列

因此,呼叫将立即发送到api。如果它在一秒钟内没有返回,那么将每秒进行另一次呼叫。一旦有来自api调用之一的响应,定时器将被取消并且整个序列重新开始。这不会取消我认为符合您意图的飞行请求。

编辑:如果稍后的请求在之前的请求之前返回,则先前的请求将被抛弃。

答案 2 :(得分:1)

在我提出仅基于rxjs并且没有副作用(没有变量分配)且没有背压的答案之前,我确实需要考虑15分钟。

const { Observable } = Rx;

const mockHttpRequest = url =>
  Observable
    .of('ok')
    .do(x => console.log('fetching...'))
    .delay(250);

const poll = (httpRequest$, ms) => {
  const tick$ = Observable.timer(ms);

  return Observable
    .zip(httpRequest$, tick$)
    .repeat()
    .map(([httpResult]) => httpResult);
};

poll(mockHttpRequest('your-url-here'), 1000)
  .do(console.log)
  .subscribe();

这是一个有效的Plunkr:https://plnkr.co/edit/sZTjLedNCE64bgLNhnaS?p=preview