Асинхронные действия

В отличие от синхронных запросов, которые выполняются здесь и сейчас, асинхронные растянуты во времени. Каждый асинхронный запрос можно представить конечным автоматом с тремя состояниями "something requested", "answer received" или "request failed". Почему это важно? Как минимум нам важно знать, когда запрос был выполнен, чтобы оповестить пользователя и произвести необходимые изменения. Но также важно знать, когда запрос начался.

Предположим, что пользователь заполнил форму создания новой задачи и нажал "создать", а потом быстро нажал "создать" ещё раз до того, как запрос успел выполниться. Такая ситуация нередко встречается и с обычными формами без JS. Она приводит к тому, что на сервере создаются две одинаковые сущности либо выскакивают ошибки, связанные с валидацией. Правильное решение в подобной ситуации связано с необходимостью менять интерфейс так, чтобы повторная отправка стала невозможной. Как правило, всё сводится к блокированию кнопки отправки с крутящимся спинером. Соответственно, после окончания запроса кнопку необходимо разблокировать или, если того требует UI, вообще убрать форму. То же самое нужно делать не только в случае успеха, но и в случае провала, иначе может получиться ситуация, что пользовательский запрос провалился, а кнопка осталась заблокирована навсегда (до перезагрузки страницы).

С точки зрения нашего React-Redux приложения автомат будет состоять из состояния в контейнере и трёх событий:

  • TASK_UPDATE_REQUEST.
  • TASK_UPDATE_SUCCESS.
  • TASK_UPDATE_FAILURE.

Именование в стиле request, success и failure — рекомендация самого Redux. Желательно всегда придерживаться именно её в случаях, когда состояния три. Большинство запросов укладываются именно в эту схему.

export const updateTaskRequest = createAction('TASK_UPDATE_REQUEST');
export const updateTaskSuccess = createAction('TASK_UPDATE_SUCCESS');
export const updateTaskFailure = createAction('TASK_UPDATE_FAILURE');

Действия, описанные выше, по сути, синхронны. А где тогда выполняется сам запрос?

Для этого вводится понятие асинхронные действия (async actions). И если в React для работы с асинхронными вызовами ничего дополнительно делать не нужно, то Redux из коробки это не умеет. Наиболее простой способ начать выполнять запросы на сервер — подключить библиотеку redux-thunk. Она представляет из себя мидлвар, который нужно не забыть подключить:

import thunk from 'redux-thunk';

const store = createStore(
  reducers,
  /* ... */,
  applyMiddleware(thunk),
);

На этом интеграция заканчивается. Следующим шагом создаются сами действия:

export const updateTask = (id, values) => async (dispatch) => {
  dispatch(updateTaskRequest());
  try {
    const response = await axios.patch(routes.taskUrl(id), { task: values });
    dispatch(updateTaskSuccess({ task: response.data }));
  } catch (e) {
    // Обязательно выводите ошибку, иначе вы не узнаете что пошло не так при отладке
    console.log(e);
    dispatch(updateTaskFailure());
  }
};

В отличие от синхронных действий, асинхронное — функция, даже две функции. Внешняя функция принимает те параметры, которые нам нужны, а вот внутренняя, асинхронная, принимает на вход функцию dispatch. Кстати, эту внутреннюю функцию вызывать не придётся, вся грязная работа делается автоматически за счёт проброса действий через контейнер. Работа этого действия для нашей ситуации описывается так:

  1. Уведомляем Redux о начале внешнего запроса.
  2. Выполняем внешний запрос.
  3. Если запрос выполнился удачно, уведомляем Redux и передаём туда полученные данные (если есть).
  4. Если запрос выполнился неудачно, уведомляем Redux.

Этот паттерн встречается в реальных приложениях крайне часто.

Посмотрим, как в коде React вызывается обработчик, выполняющий наше действие:

class EditTaskForm extends React.Component {
  handleUpdateTask = (values) => {
    const { updateTask, task } = this.props;
    updateTask(task.id, values);
  }

  render() {
    const { taskCreatingState } = this.props;
    const disabled = taskCreatingState === 'requested';

    return (
      <form action="" className="form-inline" onSubmit={this.props.handleSubmit(this.handleUpdateTask)}>
        <div className="form-group mx-3">
          <Field name="text" required component="input" type="text" />
        </div>
        <button type="submit" disabled={disabled} className="btn btn-primary btn-sm">Update</button>
      </form>
    );
  }
}

export default reduxForm({
  form: 'editTask',
})(EditTaskForm);

Из кода выше видно, что действие вызывается как обычно.

Библиотека redux-thunk всего лишь один из многих способов работы с асинхронными действиями в Redux. Существуют и другие пакеты, дающие больший контроль и больший уровень автоматизации. Но, как правило, они сложнее в понимании.

Для полного доступа к курсу, нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам Хекслета, даст возможность обращаться за помощью к менторам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг».