#ecmascript-6 #rxjs #rxjs5
#ecmascript-6 #rxjs #rxjs5
Вопрос:
Я хотел бы запрашивать API каждые x секунд при подписке на наблюдаемый автообновитель, убедившись, что последний запрос завершен, прежде чем отправлять другой.
let autoRefresher = new Observable().exhaustMap(() => Observable.defer(() => {
return someService.returningAPromise();
}).timeout(refreshIntervalInMs).repeat());
Есть ли лучший способ сделать это? Как я могу обновить интервал обновления, не создавая каждый раз новую наблюдаемую?
Ответ №1:
Я бы сделал это так:
import {Observable} from 'rxjs';
function doRequest() {
if (Math.random() < 0.25) {
return Observable.of('HTTP Response').delay(3000);
} else {
return Observable.of('HTTP Response');
}
}
let autoRefresher = Observable.timer(0, 1000)
.exhaustMap(doRequest)
.subscribe(response => {
console.log(response);
});
Смотрите живую демонстрацию: http://plnkr.co/edit/7HAib10r6Vdl1x2U2wFS
Это случайным образом вызывает задержку в 3 секунды. Оператор timer()
периодически выдает значение. Затем exhaustMap() подписывается на предыдущий наблюдаемый и игнорирует все наблюдаемые, отправленные до завершения текущего. So timer()
выдает значения, но они игнорируются exhaust()
.
Кстати, обратите внимание, что я использую TypeScript.
Комментарии:
1. Приятно, проще, чем я думал. Из интереса, для чего нужен Math.random() ?
2. @sqwk Это только для имитации запросов, которые перекрываются.
Ответ №2:
Я вижу, что это довольно старая проблема, я просто хотел предложить, возможно, более «стабильное» решение. Проблема с этим подходом заключается в том, что могут быть окна, в которых запрос не выполняется, поскольку таймер выполняется с фиксированным интервалом в 1 сек.
Например:
Интервал 1 с, длительность запроса < 1 с: каждые 1 секунду выполняется запрос, но если для выполнения запроса требуется, например, 400 мс, до запуска следующего запроса потребуется всего 600 мс (не 1 секунда) Интервал 1 с, длительность запроса 1,5 с: запрос начинается с начала, в следующем интервале запрос все еще выполняется, поэтому exhaustMap фильтрует его, и в 3-м интервале запрос будет выполняться снова (500 мс после предыдущего запроса)
В моем примере запрос всегда будет ждать 1 секунду перед повторным запуском
import { delay, flatMap, repeat, takeUntil } from 'rxjs/operators';
import { of,Subject } from 'rxjs';
function doRequest() {
if (Math.random() < 0.25) {
return Observable.of('HTTP Response').delay(3000);
} else {
return Observable.of('HTTP Response');
}
}
const subject: Subject<boolean> = new Subject();
of(null)
.pipe(
// can be used to cancel
takeUntil(subject),
// does request
flatMap(doRequest),
// delays by 1second
delay(1000),
// repeat steps above
repeat(),
)
.subscribe(response => {
console.log(respons);
});
Комментарии:
1. Однако это задерживает первый запрос, а также любые последующие элементы устаревают к моменту их отправки подписчику. Так что, если, например, вы хотите, чтобы интервал составлял 60 секунд, запрос завершится, будет отложен на 60 секунд, и к тому времени данные могут устареть.
2. первый запрос фактически не задерживается, проверьте документы: learnrxjs.io/learn-rxjs/operators/utility/delay или стековый блиц stackblitz.com/edit /… . Я не знаю, что вы имеете в виду, что последующие элементы устарели?
Ответ №3:
Важно, чтобы takeUntil(..) был последним вызовом в pipe() . В противном случае наблюдаемые не могут быть отменены. Смотрите https://cartant.medium.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef