# Асинхронные actions

*ОБНОВЛЕНИЕ 2018: Вышло* [*второе издание*](https://maxfarseer.gitbooks.io/redux-course-ru-v2/content/) *(современный код и версии пакетов, данное издание УСТАРЕЛО)*

*На* [*канале*](http://bit.ly/2IqH74P) *так же проводятся бесплатные вебинары, публикуются переводы и авторские материалы,* [*присоединяйтесь*](http://bit.ly/2IqH74P)*!*

## Асинхронные actions

Давайте представим синхронное действие: 1. Пользователь кликнул на кнопку 2. dispatch action `{type: ТИП_ДЕЙСТВИЯ, payload: доп.данные}` 3. интерфейс обновился

Давайте представим асинхронное действие: 1. Пользователь кликнул на кнопку 2. dispatch action `{type: ТИП_ДЕЙСТВИЯ_ЗАПРОС}` 3. запрос выполнился успешно 4. dispatch action `{type: ТИП_ДЕЙСТВИЯ_УСПЕШНО, payload: доп.данные}` 4. запрос выполнился неудачно 5. dispatch action `{type: ТИП_ДЕЙСТВИЯ_НЕУДАЧНО, error: true, payload: доп.данные ошибки}`

Благодаря такой схеме, в reducer'e мы сможем реализовать подобное:

```javascript
switch(тип_действия)
    case ТИП_ДЕЙСТВИЯ_ЗАПРОС:
        покажи preloader
    case ТИП_ДЕЙСТВИЯ_УСПЕШНО:
        скрой preloader, покажи данные
    case ТИП_ДЕЙСТВИЯ_НЕУДАЧНО:
        скрой preloader, покажи ошибку
```

Как нам известно, действие - это простой объект, который возвращается функцией его создающей (*action creator*).

Убедимся в этом:

*src/actions/PageActions.js*

```javascript
import { SET_YEAR } from '../constants/Page'
export function setYear(year) {
  return {
    type: SET_YEAR,
    payload: year
  }
}
```

Было бы неплохо иметь возможность возвращать не простой объект, а функцию, внутри которой иметь доступ к методу `dispatch`, и вызывать его с необходимым типом действия. Псевдокод, мог бы выглядеть так:

```javascript
export function getPhotos(year) {
  return (dispatch) => {
    dispatch({
      type: GET_PHOTOS_REQUEST
    })

    $.ajax(url)
      .success(
        dispatch({
          type: GET_PHOTOS_SUCCESS,
          payload: response.photos
        })
      )
      .error(
        dispatch({
          type: GET_PHOTOS_FAILURE,
          payload: response.error,
          error: true
        })
      )
  }
}
```

Но вот незадача, actions - это простой объект, и если action creator возвращает не простой объект, а функцию, то это как-то... Подождите! Ведь это именно то, что нам нужно: Если action creator возвращает не простой объект, а функцию - выполни ее, иначе если это простой объект ... тадам, **передай дальше**. Более того, благодаря `applyMiddleware` у нас как раз есть доступный метод *dispatch*! И еще бонусом *getState*.

Отлично, мы только что поняли, что нам нужен еще один усилитель. Такой усилитель уже написан, причем [код его](https://github.com/gaearon/redux-thunk/blob/master/src/index.js) невероятно прост, я даже приведу его здесь:

*усилитель: redux-thunk*

```javascript
function thunkMiddleware({ dispatch, getState }) {
  return next => action =>
    typeof action === 'function' ?
      action(dispatch, getState) :
      next(action);
}

module.exports = thunkMiddleware
```

Нам остается лишь добавить зависимость в наш проект, и убедиться, что у нас redux версии, не ниже 3.1.0

```
npm update redux --save
npm install redux-thunk --save
```

Для практики, предлагаю написать следующее:

* по клику на кнопку с номером года
  * меняется год в заголовке
  * ниже (где должны быть фото), появляется текст "Загрузка..."
* после удачной загрузки\*
  * убрать текст "Загрузка..."
  * отобразить строку "У тебя ХХ фото" (зависит, от длины массива, переданного в action.payload)

\* *вместо реального метода загрузки, использовать setTimeout, который является удобным для тренировок исполнения асинхронных запросов.*

Вы можете попробовать выполнить это задание сами, а потом сравнить его с решением ниже.

Для отображения / скрытия фразы "*Загрузка...*", используйте в reducer'е еще одно свойство у состояния. Например, *fetching*:

```javascript
const initialState = {
  year: 2016,
  photos: [],
  fetching: false
}
```

Решение ниже.

Для начала изменим набор констант:

*src/constants/Page.js*

```javascript
export const GET_PHOTOS_REQUEST = 'GET_PHOTOS_REQUEST'
export const GET_PHOTOS_SUCCESS = 'GET_PHOTOS_SUCCESS'
```

Далее добавим новый усилитель: *src/store/configureStore.js*

```javascript
import { createStore, applyMiddleware } from 'redux'
import rootReducer from '../reducers'
import createLogger from 'redux-logger'
import thunk from 'redux-thunk' // <-- добавили redux-thunk


export default function configureStore(initialState) {
  const logger = createLogger()
  const store = createStore(
    rootReducer,
    initialState,
    applyMiddleware(thunk, logger)) // <-- добавили его в цепочку перед logger'ом

  if (module.hot) {
    module.hot.accept('../reducers', () => {
      const nextRootReducer = require('../reducers')
      store.replaceReducer(nextRootReducer)
    })
  }

  return store
}
```

Изменим action creator: *src/actions/PageActions.js*

```javascript
import {
  GET_PHOTOS_REQUEST,
  GET_PHOTOS_SUCCESS
} from '../constants/Page'

export function getPhotos(year) {

  return (dispatch) => {
    dispatch({
      type: GET_PHOTOS_REQUEST,
      payload: year
    })

    setTimeout(() => {
      dispatch({
        type: GET_PHOTOS_SUCCESS,
        payload: [1,2,3,4,5]
      })
    }, 1000)
  }
}
```

Изменим reducer: *src/reducers/page.js*

```javascript
import {
  GET_PHOTOS_REQUEST,
  GET_PHOTOS_SUCCESS
} from '../constants/Page'

const initialState = {
  year: 2016,
  photos: [],
  fetching: false
}

export default function page(state = initialState, action) {

  switch (action.type) {
    case GET_PHOTOS_REQUEST:
      return { ...state, year: action.payload, fetching: true }

    case GET_PHOTOS_SUCCESS:
      return { ...state, photos: action.payload, fetching: false }

    default:
      return state;
  }

}
```

У нас готова логика для обновления состояния (и интерфейса, разумеется). Осталось поправить отображение.

Так как мы переписали и переименовали функцию (setYear -> getPhotos):

*src/containers/App.js*

```javascript
...
    const { getPhotos } = this.props.pageActions

    return <div className='row'>
      <Page photos={page.photos} year={page.year} getPhotos={getPhotos} fetching={page.fetching}/>
...
```

Причем, в `mapDispatchToProps` - нам ничего менять не нужно, так как мы по прежнему присоединяем все pageActions в props контейнера `<App />`

Обновим соответствующий компонент: *src/components/Page.js*

```javascript
import React, { PropTypes, Component } from 'react'

export default class Page extends Component {
  onYearBtnClick(e) {
    this.props.getPhotos(+e.target.innerText)
  }
  render() {
    const { year, photos, fetching } = this.props
    return <div className='ib page'>
      <p>
        <button className='btn' onClick={::this.onYearBtnClick}>2016</button>{' '}
        <button className='btn' onClick={::this.onYearBtnClick}>2015</button>{' '}
        <button className='btn' onClick={::this.onYearBtnClick}>2014</button>
      </p>
      <h3>{year} год</h3>
      {
        fetching ?
        <p>Загрузка...</p>
        :
        <p>У тебя {photos.length} фото.</p>
      }
    </div>
  }
}

Page.propTypes = {
  year: PropTypes.number.isRequired,
  photos: PropTypes.array.isRequired,
  getPhotos: PropTypes.func.isRequired
}
```

Когда будете проверять работу в браузере, обратите внимание на логгер. Он все так же работает и информативен.

Пока мы писали код для асинхронного запроса, мы НЕ нарушили главные принципы redux-приложения: 1. Мы всегда возвращали новое состояние (новый объект, смотрите *src/reducers/page.js*) 2. Мы строго следовали однонаправленному потоку данных в приложении: *юзер кликнул - возникло действие - редьюсер изменил - компонент отобразил*.

**Итого**: вы можете сами дописать наше приложение, чтобы оно взаимодействовало с VK, так как все что нужно, это добавить реальный асинхронный запрос (точнее парочку - для логина, и для получения фото). Ложку дегтя добавляет тот факт, что для этого потребуется создать в интерфейсе VK приложение, и выполнять наши запросы с реального сервера, так как VK.API не работает с localhost.

Об этом мы и поговорим в следующей главе.

[Исходный код](https://github.com/maxfarseer/redux-ru-tutorial/tree/async_actions) на данный момент.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://max-frontend.gitbook.io/redux-course-ru/struktura_prilozheniya/asinhronnie_actions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
