Skip to content

Операторы

Каждая inline-опция createQuery — это сахар над standalone-оператором. Импортируй и применяй к любому query/mutation (в т.ч. созданному в другом месте). Композируемы и tree-shakeable.

ts
import {
  concurrency,
  retry,
  cache,
  timeout,
  debounce,
  fallback,
  keepFresh,
  applyBarrier,
} from 'effector-refetch';

concurrency

Поведение пересекающихся прогонов: TAKE_LATEST (по умолчанию), TAKE_FIRST, TAKE_EVERY, QUEUE.

ts
concurrency(searchQuery, { strategy: 'TAKE_LATEST' }); // новый прогон отменяет предыдущий
concurrency(saveMutation, { strategy: 'QUEUE' }); // записи идут строго по очереди

QUEUE сериализует прогоны: следующий стартует только после завершения предыдущего, ошибки цепочку не рвут, а cancel/reset смывают ожидающих (они абортятся как 'cancelled'). В сочетании с ключом полосы сериализация действует внутри полосы.

Добавьте ключ полосы (lane), чтобы прогоны конкурировали только с прогонами того же ключа — обновление одной строки таблицы больше не отменяет соседние:

ts
concurrency(rowQuery, { strategy: 'TAKE_LATEST', key: ({ rowId }) => String(rowId) });

Вытеснение (TAKE_LATEST) и отбрасывание при занятости (TAKE_FIRST) действуют внутри полосы; cancel / reset по-прежнему затрагивают все полосы. $data у query остаётся один — полосы разделяют отмену, а не данные (последний завершившийся прогон записывает стор; данные по ключу храните в кэшируемом query с ключом из params).

Попробуйте вживую — три слота покедекса поверх настоящего PokeAPI, каждый слот — полоса:

Запускаемый скрипт: examples/concurrency-lanes.ts.

retry

retry(query, 3) или конфиг. Каждая попытка — реальный вызов эффекта; filter решает, какие сбои ретраить, suppressIntermediateErrors держит $error чистым до финальной попытки.

ts
import { exponentialDelay } from 'effector-refetch';

retry(userQuery, {
  times: 3,
  delay: exponentialDelay(200),
  filter: ({ error }) => (error as RequestError).status !== 404, // не ретраить 404
});

cache

cache(query) (in-memory) или конфиг (adapter / staleAfter / key / swr / dedupe / purge / fillOnAbort).

ts
cache(productsQuery, { staleAfter: 30_000, swr: true, purge: loggedOut });
cache(feedQuery, { swr: { silent: true }, fillOnAbort: true });
  • swr: { silent: true } — упавшая фоновая ревалидация продолжает тихо отдавать stale-запись: $error/$status не тронуты, finished.fail для наблюдателей всё же срабатывает (и startAsync реджектится).
  • fillOnAbort: true — ВЫТЕСНЕННОМУ рану дают долететь, чтобы его ответ всё-таки лёг в кэш; рвётся только его связь с $data/статусом. Явный cancel/reset абортит по-настоящему.

timeout

Дедлайн одной попытки (мс): прерывает запрос в полёте и роняет прогон (ретраябельно), если превышен. 0 — выкл. Не путать с refetchInterval (частота поллинга).

ts
timeout(reportQuery, 5000); // сдаёмся на одной попытке через 5с

debounce

Подождать N мс перед запуском; более новый запуск той же полосы, стартовавший во время ожидания, вытесняет этот до похода в сеть — честный debounce для поиска по мере ввода под TAKE_LATEST. 0 отключает.

ts
debounce(searchQuery, 300); // или createQuery({ debounce: 300 })

fallback

Превратить финальную ошибку (после ретраев) в данные: значение попадает в $data, $status становится done, срабатывает finished.done. Значение не пишется в кэш (это не серверная истина), aborts/skips не затрагиваются. null отключает.

ts
fallback(productsQuery, []); // пустой список вместо экрана ошибки
fallback(profileQuery, ({ error, params }) => cachedProfileOr(params, error));

keepFresh

Рефетчит запрос его последними параметрами при изменении стора-source или при срабатывании @@trigger — свежесть по зависимости (фильтры, локаль, пользователь, успешная мутация, ping по websocket). No-op до первого запуска и пока disabled.

ts
keepFresh(productsQuery, { source: $filters }); // или source: [$filters, $locale]

// triggers: что угодно с протоколом @@trigger или обычный effector Event
keepFresh(productsQuery, { triggers: [createProductMutation, tabFocused] });

triggers принимает наши же query/mutation (они реализуют @@triggerfired = finished.done), триггеры web-API из withease, совместимые с farfetched триггеры или сырой Event. У каждого триггера setup дёргается один раз при подключении и остаётся активным на всё время жизни приложения.

Протокол @@trigger

Каждый query и mutation является @@trigger: query['@@trigger']() возвращает { fired, setup, teardown }, где fired — это finished.done. Поэтому запрос можно отдавать в keepFresh({ triggers }) самого farfetched (и наоборот), либо в любого потребителя протокола:

ts
import { keepFresh } from '@farfetched/core';

keepFresh(someFarfetchedQuery, { triggers: [ourQuery] }); // ourQuery успешен → farfetched рефетчит

isTrigger(x) сужает тип к протоколу. Наши юниты — always-on триггеры: setup/teardown есть для совместимости с протоколом, но не гейтят срабатывание (запрос живёт своим scoped-жизненным циклом).

applyBarrier

Навешивает на готовый query/mutation barrier (например 401 → обновить токен → продолжить). null — отвязать.

ts
const auth = createBarrier({ perform: refreshTokenFx });
applyBarrier(userQuery, auth);

Повторное применение оператора

Два чётко определённых поведения, по типу оператора:

  • Last-winsconcurrency / retry / cache / timeout / debounce / fallback / applyBarrier это engine-сеттеры: второй вызов заменяет первый. retry(q, 1); retry(q, 3) ⇒ 3 ретрая; applyBarrier(q, null) отвязывает.
  • АддитивноkeepFresh / invalidate / update добавляют проводку на каждый вызов: два зарегистрированных keepFresh-источника — рефетч на изменение любого из них.

Это намеренно и покрыто тестом (test/multi-operators.test.ts) — last-wins для одно-значных конфиг-ручек, аддитивно для тех, что регистрируют реакции.


Всё это эквивалентно соответствующей опции createQuery({ … }) — выбирай, что читается лучше. Рабочий пример: examples/operators.ts.

Под лицензией MIT