我希望轮询端点的速度不会超过每秒一次,并且不会慢于轮询端点所需的时间。不应该有多个未完成的请求。
我想要一种反应式编程方式,每秒至少轮询一次端点,但如果端点花费的时间超过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);
}
}
答案 0 :(得分:4)
这是我的解决方案。它使用内部主题combineLatest
和filter
来确保如果回复的响应速度低于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;
我刚刚想到有一个运营商正是这样做的:exhaustMap
。
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;
答案 1 :(得分:3)
我相信这可以做你想要的:
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;
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