Асинхронность в effector с помощью эффектов
Асинхронность — это базовая часть любого современного приложения, и Effector предоставляет удобные инструменты для её обработки. С помощью эффектов (createEffect) можно построить предсказуемую логику работы с асинхронными данными.
Команда Effector рекомендует использовать Fx
постфикс для названия эффектов, это не является обязательным требованием, а рекомендацией к использованию.
Что такое эффекты?
Эффекты (Effect) — это инструмент Effector для работы с внешними api, или для сторонних эффектов вашего приложения, например:
- Асинхронные запросы на сервер
- Работа с
localStorage
/indexedDB
- Любые операции, которые могут либо выполниться либо выкинуть ошибку, или выполняться какое-то время
Эффект может быть как асинхронный, так и синхронный.
Основные состояния эффектов
Работать с эффектами очень удобно благодаря встроенным состояниям и событиям, которые автоматически отслеживает состояние выполнения эффекта:
pending
— является стором указывает, выполняется ли эффект, полезно для отображения загрузки.done
— является событием, срабатывает при успешном завершении.fail
— является событием, срабатывает при ошибке.finally
— является событием, срабатывает когда эффект заверешен с ошибкой или успешно.
С полным api effect
можно познакомиться здесь.
Не стоит вызывать события или модифицировать состояния эффекта в ручную, effector сам сделает это.
const fetchUserFx = createEffect(() => { /* вызов внешнего api */});
fetchUserFx.pending.watch((isPending) => console.log("Pending:", isPending));
fetchUserFx.done.watch(({ params, result }) => console.log(`Fetched user ${params}:`, result));
fetchUserFx.finally.watch((value) => { if (value.status === "done") { console.log("fetchUserFx resolved ", value.result); } else { console.log("fetchUserFx rejected ", value.error); }});
fetchUserFx.fail.watch(({ params, error }) => console.error(`Failed to fetch user ${params}:`, error),);
fetchUserFx();
Привязка эффектов к событиям и сторам
Заполнить стор данными при завершении эффекта
Допустим мы хотим, чтобы при завершении работы эффекта effector взял данные, которые вернул эффект, и обновил стор с новыми данными, сделать это довольно просто при помощи событий эффекта:
import { createStore, createEffect } from "effector";
const fetchUserNameFx = createEffect(async (userId: string) => { const userData = await fetch(`/api/users/${userId}`);
return userData.name;});
const $error = createStore<string | null>(null);const $userName = createStore("");const $isLoading = fetchUserNameFx.pending.map((isPending) => isPending);
$error.reset(fetchUserNameFx.done);
$userName.on(fetchUserNameFx.done, (_, { params, result }) => result);$error.on(fetchUserNameFx.fail, (_, { params, error }) => error.message);// или 🔃$userName.on(fetchUserNameFx.doneData, (_, result) => result);$error.on(fetchUserNameFx.failData, (_, error) => error.message);
$isLoading.watch((loading) => console.log("Is loading:", loading));
doneData
и failData
являются событиями, которые идентичны done
и fail
соответственно, за исключением того, что они получают только result
и error
в свои параметры.
Вызов эффекта при срабатывании события
В большинстве случаев вы захотите вызвать эффект при срабатывании какого-нибудь события, например подтверждение формы, или нажатие на кнопку, в таких случаях вам поможет метод sample
, который вызовет target
, при срабатывании clock
.
Функция sample
является ключевым элементом для связывания юнитов. Она позволяет вам гибко и легко настроить реактивную логику вашего приложения.
import { createEvent, sample } from "effector";
const userLoginFx = createEffect(() => { // какая-то логика});
// Событие для загрузки данныхconst formSubmitted = createEvent();
// Связываем событие с эффектомsample({ clock: formSubmitted, // Когда сработает target: userLoginFx, // Запусти это});
// где-то в приложенииformSubmitted();
Обработка ошибок в эффектах
Effector предоставляет надежные возможности обработки ошибок. Когда во время выполнения эффекта происходит ошибка, она автоматически перехватывается и обрабатывается через событие fail
.
Чтобы типизировать ошибку в эффекте, необходимо передать определенный тип в generic третьим параметром функции createEffect
:
import { createEffect } from "effector";
class CustomError extends Error { // реализация}
const effect = createEffect<Params, ReturnValue, CustomError>(async () => { const response = await fetch(`/api/users/${userId}`);
if (!response.ok) { // Вы можете выбрасывать ошибки, которые будут перехвачены обработчиком .fail throw new CustomError(`Не удалось загрузить пользователя: ${response.statusText}`); }
return response.json();});
Если вы выбросите ошибку другого типа, TypeScript покажет вам ошибку.
Практический пример
Рассмотрим реальный пример, где пользователь вводит ID, а по нажатию кнопки загружаются данные о нём.
import { createStore, createEvent, createEffect, sample } from "effector";
// Эффект для загрузки данныхconst fetchUserFx = createEffect(async (id: number) => { const response = await fetch(`/api/user/${id}`);
if (!response.ok) { // можно модифицировать ошибку, прежде чем она попадет в fail/failData throw new Error("User not found"); }
return response.json();});
const setId = createEvent<number>();const submit = createEvent();
const $id = createStore(0);const $user = createStore<{ name: string } | null>(null);const $error = createStore<string | null>(null);const $isLoading = fetchUserFx.pending;
$id.on(setId, (_, id) => id);$user.on(fetchUserFx.doneData, (_, user) => user);$error.on(fetchUserFx.fail, (_, { error }) => error.message);$error.reset(fetchUserFx.done);
// Логика загрузки: запускаем fetchUserFx при submitsample({ clock: submit, source: $id, target: fetchUserFx,});
// ИспользованиеsetId(1); // Устанавливаем IDsubmit(); // Загружаем данные
Ознакомиться с полным API для эффектов
Документация на английском языке - самая актуальная, поскольку её пишет и обновляет команда effector. Перевод документации на другие языки осуществляется сообществом по мере наличия сил и желания.
Помните, что переведенные статьи могут быть неактуальными, поэтому для получения наиболее точной и актуальной информации рекомендуем использовать оригинальную англоязычную версию документации.