← Назад к вопросам

Как измеряется стандарт языка JavaScript?

1.3 Junior🔥 71 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Функции-генераторы в Redux Saga

Функции-генераторы (generator functions) — это специальный механизм JavaScript, используемый в Redux Saga для управления асинхронными операциями и сложной логикой побочных эффектов.

Что такое генераторы

Генератор — это функция, которая может прерваться и возобновить свое выполнение. Определяется с помощью function* и использует yield:

function* simpleGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = simpleGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

Генераторы в Redux Saga

Redux Saga использует генераторы для написания асинхронного кода в синхронном стиле:

import { put, call, takeEvery } from 'redux-saga/effects';
import * as API from './api';

// Saga генератор
function* fetchUserSaga(action) {
  try {
    // call эффект — выполняет асинхронную функцию
    const user = yield call(API.fetchUser, action.payload.id);
    
    // put эффект — диспетчит action
    yield put({
      type: 'FETCH_USER_SUCCESS',
      payload: user
    });
  } catch (error) {
    yield put({
      type: 'FETCH_USER_ERROR',
      payload: error.message
    });
  }
}

// Корневая saga, которая слушает actions
function* watchUserSaga() {
  yield takeEvery('FETCH_USER_REQUEST', fetchUserSaga);
}

Основные эффекты Redux Saga

// call — вызвать функцию и ждать результата
const data = yield call(fetch, '/api/users');

// put — диспетчить action в store
yield put({ type: 'ACTION_NAME', payload: data });

// select — получить состояние из store
const users = yield select(state => state.users);

// takeEvery — слушать каждый action
yield takeEvery('ACTION_TYPE', saga);

// takeLatest — слушать последний action (отменяет предыдущие)
yield takeLatest('SEARCH_REQUEST', searchSaga);

// fork — запустить saga без ожидания
yield fork(backgroundSaga);

// all — запустить несколько саг параллельно
yield all([fork(saga1), fork(saga2)]);

// delay — задержка
yield delay(1000);

Практический пример: Загрузка данных

function* fetchPostsSaga(action) {
  yield put({ type: 'FETCH_POSTS_START' });
  
  try {
    // Вызов API
    const posts = yield call(
      fetch,
      `https://api.example.com/posts?page=${action.payload}`
    ).then(res => res.json());
    
    // Успешно получили данные
    yield put({
      type: 'FETCH_POSTS_SUCCESS',
      payload: posts
    });
  } catch (error) {
    // Обработка ошибки
    yield put({
      type: 'FETCH_POSTS_ERROR',
      payload: error.message
    });
  }
}

function* watchFetchPostsSaga() {
  yield takeLatest('FETCH_POSTS_REQUEST', fetchPostsSaga);
}

Сложный пример: Обработка несколько действий

function* loginSaga(action) {
  const { email, password } = action.payload;
  
  try {
    // Показать загрузку
    yield put({ type: 'LOGIN_START' });
    
    // Запрос на сервер
    const response = yield call(API.login, email, password);
    
    // Сохранить token в localStorage
    localStorage.setItem('token', response.token);
    
    // Диспетчить успех
    yield put({
      type: 'LOGIN_SUCCESS',
      payload: response.user
    });
    
    // Перейти на главную страницу
    yield call(navigateTo, '/dashboard');
  } catch (error) {
    yield put({
      type: 'LOGIN_ERROR',
      payload: error.message
    });
  }
}

function* watchLoginSaga() {
  yield takeEvery('LOGIN_REQUEST', loginSaga);
}

Преимущества генераторов в Saga

  1. Синхронный стиль кода — асинхронность скрыта за yield
// Вместо callback hell
fetch(...).then(res => 
  res.json().then(data => 
    dispatch(success(data))
  )
);

// Используем удобный синхронный стиль
const data = yield call(fetch, ...);
yield put(success(data));
  1. Легко тестировать — каждый yield возвращает effect объект
it('should fetch user', () => {
  const gen = fetchUserSaga({ payload: { id: 1 } });
  
  expect(gen.next().value)
    .toEqual(call(API.fetchUser, 1));
});
  1. Обработка ошибок через try/catch

  2. Отмена операций — через механизм отмены саг

Отмена саг

function* watchSearchSaga() {
  // takeLatest отменяет предыдущую сагу если пришла новая
  yield takeLatest('SEARCH_REQUEST', searchSaga);
}

function* searchSaga(action) {
  try {
    // Эта операция будет отменена если придет новый SEARCH_REQUEST
    const results = yield call(API.search, action.payload);
    yield put({ type: 'SEARCH_SUCCESS', payload: results });
  } catch (error) {
    if (error.isCancelled) {
      console.log('Search was cancelled');
    }
  }
}

Современные альтернативы

Хотя Redux Saga популярна, есть альтернативы:

  • Redux Thunk — проще, но менее мощная
  • Redux Observable — использует RxJS
  • RTK Query — встроен в Redux Toolkit
  • TanStack Query — универсальное решение для data fetching
// Пример с современным Redux Toolkit
const fetchUserSlice = createAsyncThunk(
  'users/fetchUser',
  async (userId) => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }
);

Почему использовать Saga

  1. Сложная логика побочных эффектов — саги справляются лучше
  2. Интеграция с Redux — всё в одном store
  3. Тестируемость — effects легко mock'ировать
  4. Предсказуемость — весь поток данных в Redux

Итог: функции-генераторы в Redux Saga предоставляют элегантный способ управления асинхронными операциями в Redux приложениях, хотя сегодня есть более современные альтернативы.