Построить клиент фича-флагов, опрашивающий обновления, с управлением start/stop
Реализуйте TogglesClient, поддерживающий свежесть карты фича-флагов опросом эндпоинта. Конструктор принимает (initialData, { url, interval }). Сделайте getToggle(key) (читает текущее значение), start()/stop() (запуск/остановка опроса) и forceUpdate() (немедленный перезапрос). Требование: никогда не давать двум обновлениям перекрыться, даже если запрос медленный.
class TogglesClient {
constructor(initialData, { url, interval }) {
// ваш код здесь
}
}
Допишите реализацию.
Планировать через setTimeout, а не setInterval: в колбэке сделать await запроса, затем перепланировать в .finally, чтобы медленный запрос задерживал следующий опрос, а не перекрывал его. forceUpdate обновляет сейчас; getToggle читает data[key]; stop зовёт clearTimeout; start снова взводит таймер. Самоперепланирование лишь после завершения и не даёт перекрыться обновлениям в полёте.
- ✗Использовать
setInterval, который может наслаивать перекрывающиеся запросы, когда запрос медленнее интервала - ✗Перепланировать до разрешения запроса, а не в
.finally, снова порождая перекрытие - ✗Забывать
clearTimeoutвstop(), оставляя опрос всё ещё ожидающим
- →Почему самоперепланируемый
setTimeoutизбегает перекрытия запросов, которое допускаетsetInterval? - →Почему перепланировать в
.finally, а не в обработчике успеха?
Решение
Самоперепланируемый setTimeout в .finally гарантирует отсутствие перекрытий.
class TogglesClient {
#data;
#url;
#interval;
#timer = null;
constructor(initialData, { url, interval }) {
this.#data = initialData;
this.#url = url;
this.#interval = interval;
this.start();
}
#schedule() {
this.#timer = setTimeout(() => {
this.forceUpdate().finally(() => this.#schedule());
}, this.#interval);
}
async forceUpdate() {
const res = await fetch(this.#url);
this.#data = await res.json();
}
getToggle(key) {
return this.#data[key];
}
start() {
if (!this.#timer) this.#schedule();
}
stop() {
clearTimeout(this.#timer);
this.#timer = null;
}
}
Как это работает
Опрос построен на setTimeout, который перевзводит себя в .finally — то есть только после того, как очередное обновление завершилось (успешно или с ошибкой). Поэтому следующий запрос стартует не раньше, чем завершился предыдущий: два обновления никогда не идут одновременно, даже если сеть тормозит.
setInterval так не умеет — он стреляет по таймеру независимо от того, завершился ли прошлый запрос, и медленный fetch приводит к наслоению. getToggle читает уже закэшированную карту, forceUpdate обновляет немедленно, а stop/start гасят и взводят таймер.