Обработка ошибок
Ошибки — полноценные граждане: упавший эффект пишет $error, переводит $status в 'fail' и эмитит finished.fail — всё scope-безопасно. В этом рецепте: как читать ошибки, нормализовать их, решать что ретраить и реагировать глобально.
Чтение ошибки
const userQuery = createQuery({ effect: fetchUserFx });
userQuery.$error; // Store<Error | null>
userQuery.$status; // 'initial' | 'pending' | 'done' | 'fail'
userQuery.finished.fail; // Event<{ params; error }>В компоненте биндинги отдают это напрямую:
// React
const { error, status } = useQuery(userQuery);
// Vue
const { error, isFail } = useQuery(userQuery);Нормализация через mapError
Приведите сырые сбои к форме, понятной вашему UI, ещё до попадания в $error:
const userQuery = createQuery({
effect: fetchUserFx,
mapError: ({ error, params }) => ({
code: (error as RequestError).status ?? 0,
message: error instanceof Error ? error.message : 'Unknown error',
userId: params,
}),
});
// теперь userQuery.$error — Store<{ code; message; userId } | null>Типизированные транспортные ошибки
createRequestFx реджектит RequestError с полями status и data, так что можно ветвиться по HTTP-статусу. Оборачиваете сторонний клиент? normalizeRequestError приводит ошибки в стиле axios/ofetch к той же форме:
import { createRequestFx, RequestError, normalizeRequestError } from 'effector-refetch';
const fetchUserFx = createRequestFx(async (id: number, { signal }) => {
const res = await fetch(`/api/users/${id}`, { signal });
if (!res.ok) throw new RequestError(`HTTP ${res.status}`, { status: res.status, data: await res.text() });
return res.json();
});
// из axios/ofetch:
const fetchFx = createRequestFx(async (id: number) => {
try {
return (await api.get(`/users/${id}`)).data;
} catch (e) {
throw normalizeRequestError(e); // -> RequestError { status, data }
}
});Type guards
Вместо instanceof + кастов к .status сужайте ошибки встроенными guard'ами:
import { isRequestError, isHttpError, isTimeoutError, isValidationError } from 'effector-refetch';
const message = (e: unknown) => {
if (isHttpError(e, 401)) return 'Войдите в аккаунт';
if (isHttpError(e, (s) => s >= 500)) return 'Ошибка сервера — повторите';
if (isTimeoutError(e)) return 'Таймаут';
if (isValidationError(e)) return `Плохие данные: ${e.validationErrors.join(', ')}`;
if (isRequestError(e)) return e.message; // сетевая ошибка (без status)
return 'Неизвестная ошибка';
};isHttpError(e, status?)—RequestErrorсоstatus; передайте код (404) или предикат ((s) => s >= 500).isTimeoutError(e)— запуск, прерванный поtimeout.isValidationError(e)— провал контракта /validate(сужает к.validationErrors).isRequestError(e)— любая нормализованная транспортная ошибка.
Что ретраить
По умолчанию retry повторяет при любом сбое. filter оставляет только временные (пропуская 4xx), а suppressIntermediateErrors держит $error чистым до финальной попытки:
import { isHttpError, isTimeoutError } from 'effector-refetch';
const query = createQuery({
effect: fetchUserFx,
retry: {
times: 3,
delay: (attempt) => 2 ** attempt * 200, // backoff
// сетевые ошибки, таймауты и 5xx — никогда 4xx
filter: ({ error }) =>
isTimeoutError(error) || isHttpError(error, (s) => s >= 500) || !isHttpError(error),
suppressIntermediateErrors: true, // $error остаётся null во время ретраев
},
});Провал валидации (контракты) бросает ValidationError, который идёт тем же путём и ретраится — удобно, когда нестабильный апстрим иногда отдаёт битые данные.
Глобальная реакция
finished.fail — обычное событие effector: засемплите его в тост, логгер или Sentry:
import { sample } from 'effector';
sample({
clock: [userQuery.finished.fail, todosQuery.finished.fail],
fn: ({ error }) => (error instanceof Error ? error.message : 'Request failed'),
target: showToastFx,
});С общей фабрикой это можно подключить один раз для всех запросов группы через их события finished.fail, а не повторять для каждого.
401 → обновление токена → повтор
Для сценария «токен протух, обнови и повтори упавшие запросы» не обрабатывайте это в каждом запросе — поставьте всё окружение на паузу через barrier: на 401 он блокируется, обновляет токен и затем отпускает очередь запросов.
Рабочие формы ошибок: examples/graphql.ts (GraphQL errors → RequestError) и страница HTTP.