Авто-рефетч и поллинг
Поллинг — refetchInterval
Перезапрос по таймеру, пока запрос запущен и включён:
const stats = createQuery({ effect: fetchStatsFx, refetchInterval: 5000 }); // каждые 5с
stats.start();После каждого завершения (успех или ошибка) запрос ждёт refetchInterval мс и делает refresh с последними параметрами (минуя кэш). Пауза, пока $enabled = false, остановка на reset, и всё fork-корректно (каждый scope поллит независимо). Интервал может быть реактивным — передайте Store<number> и меняйте на лету (например, чаще, пока вкладка активна):
createQuery({ effect: fx, refetchInterval: $pollMs });По фокусу окна / реконнекту
Opt-in, только браузер, tree-shakeable:
import { refetchOnWindowFocus, refetchOnReconnect } from 'effector-refetch';
const stop1 = refetchOnWindowFocus(userQuery);
const stop2 = refetchOnReconnect(userQuery);
// вызовите stop1() / stop2(), чтобы отписатьсяОба перезапрашивают с последними параметрами запроса, только если он уже запускался и включён. Без scope они читают no-scope-стор (одно-клиентское приложение); передайте scope вторым аргументом — триггер сработает fork-корректно. Безопасно вызывать на каждый mount компонента: effector-проводка создаётся один раз на запрос, отписка снимает только DOM-листенер.
Offline / сетевой режим
createNetworkBarrier() — это barrier, который блокируется, пока браузер офлайн, и разблокируется при реконнекте. Подключите его к запросам — и их прогоны встают на паузу при потере сети, а затем сами продолжаются при её возврате, без обвязки на каждый запрос:
import { createNetworkBarrier, refetchOnReconnect } from 'effector-refetch';
const offline = createNetworkBarrier();
const userQuery = createQuery({ effect: fetchUserFx, barrier: offline });
// или на всю группу: createQueryFactory({ barrier: offline })
offline.$online; // Store<boolean> — для баннера «вы офлайн»
refetchOnReconnect(userQuery); // опционально: ещё и обновить уже загруженные данные
offline.stop(); // отвязать слушатели online/offline при размонтированииПрогон, запущенный офлайн, висит в pending (тело эффекта не вызывается), пока сеть не вернётся. Только браузер — на сервере барьер остаётся открытым (онлайн).
В приложении со scope (@effector/next, любой fork()-сетап) передайте scope: слушатели online/offline срабатывают вне стека вызовов effector, иначе блокировка попадёт в бесскоупное приложение, а запросы в форке продолжат выполняться:
const offline = createNetworkBarrier({ scope });Рефетч при изменении источника — keepFresh
Держите запрос свежим относительно внешнего состояния (фильтры, локаль, текущий пользователь): keepFresh перезапрашивает его с последними параметрами при изменении стора-source.
import { keepFresh } from 'effector-refetch';
keepFresh(productsQuery, { source: $filters }); // или source: [$filters, $locale]No-op, пока запрос не запускался (status !== 'initial') и пока он disabled. Отличается от refetchInterval (по времени) — это по зависимости. (Если значение источника должно реально менять запрос — прокидывайте его в параметры через connectQuery / sample в start.)
Триггеры web-API (@withease/web-api)
Трекеры @withease/web-api реализуют протокол @@trigger, поэтому подключаются в keepFresh({ triggers }) напрямую — без адаптера:
import { trackPageVisibility, trackNetworkStatus } from '@withease/web-api';
import { keepFresh } from 'effector-refetch';
const network = trackNetworkStatus();
// рефетч, когда вкладка снова становится видимой (трекер фаерит свой `visible`)
keepFresh(dashboardQuery, { triggers: [trackPageVisibility()] });
// …или на любое обычное событие, которое отдаёт трекер
keepFresh(dashboardQuery, { triggers: [network.online] });Сторы трекеров годятся в остальные слоты: гейтите запрос пока офлайн через enabled, или используйте стор трекера как source:
createQuery({ effect: fx, enabled: network.$online });Это пересекается со встроенными refetchOnWindowFocus / refetchOnReconnect / createNetworkBarrier выше — для типовых случаев берите встроенные, а @withease/web-api — когда нужно больше сигналов (геолокация, media query, ориентация экрана, …) за тем же @@trigger API.
Композиция с patronum
Триггеры запроса — обычные события effector, поэтому их можно гонять любым оператором patronum — без специального API:
import { interval, debounce, throttle } from 'patronum';
// debounce поиска по мере ввода
debounce({ source: queryChanged, timeout: 300, target: searchQuery.start });
// свой поллинг с управлением start/stop
const { tick } = interval({ timeout: 10_000, start: pageOpened, stop: pageClosed });
sample({ clock: tick, source: searchQuery.$params, target: searchQuery.refetch });
// throttle кнопки обновления
throttle({ source: refreshClicked, timeout: 1000, target: dashboard.refresh });Для типового случая используйте встроенный refetchInterval; берите patronum, когда нужны явные start/stop, debounce или throttle.
Поллинг и SSR
При refetchInterval > 0 allSettled(query.start, { scope }) не резолвится никогда — каждое завершение планирует следующий тик, и scope не переходит в покой. Для SSR (и тестов, ожидающих scope) сделайте интервал Store<number> и обнулите его в этом scope:
const $interval = createStore(30_000);
const stats = createQuery({ effect: fetchStatsFx, refetchInterval: $interval });
// SSR-scope на запрос: поллинг выключен
const scope = fork({ values: [[$interval, 0]] });