Асинхронность в 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 при submit
sample({
clock: submit,
source: $id,
target: fetchUserFx,
});
// Использование
setId(1); // Устанавливаем ID
submit(); // Загружаем данные
Ознакомиться с полным API для эффектов
Документация на английском языке - самая актуальная, поскольку её пишет и обновляет команда effector. Перевод документации на другие языки осуществляется сообществом по мере наличия сил и желания.
Помните, что переведенные статьи могут быть неактуальными, поэтому для получения наиболее точной и актуальной информации рекомендуем использовать оригинальную англоязычную версию документации.