Redux: Как связать асинхронные действия?

Созданный на 28 апр. 2016  ·  24Комментарии  ·  Источник: reduxjs/redux

Здравствуйте, изучаю Redux и столкнулся с интересной проблемой? нужно сделать цепочку асинхронных запросов от других действий
1-получитьпользователя()
2-получить сообщение ()

У меня есть 2 решения, выполняемые после входа пользователя. Затем (dispatch ({type: GET_POST_REQUEST}))
или написать функцию в middleWare.

Как это делать правильно?

Самый полезный комментарий

Привет! Это система отслеживания проблем, а не форум поддержки. Мы будем признательны, если вы в следующий раз спросите на StackOverflow, потому что ответы здесь теряются, в отличие от SO.

Тем не менее, если вы создаете свой магазин с промежуточным ПО Redux Thunk , вы можете написать создателей асинхронных действий следующим образом:

// If you use Redux Thunk...
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))

// You can define asynchronous action creators that return functions.
// We call such action creators "thunks":

export function getUser(id) {
  // Redux Thunk will inject dispatch here:
  return dispatch => {
    // Reducers may handle this to set a flag like isFetching
    dispatch({ type: 'GET_USER_REQUEST', id })

    // Perform the actual API call
    return fetchUser().then(
      response => {
        // Reducers may handle this to show the data and reset isFetching
        dispatch({ type: 'GET_USER_SUCCESS', id,  response })
      },
      error => {
        // Reducers may handle this to reset isFetching
        dispatch({ type: 'GET_USER_FAILURE', id,  error })
        // Rethrow so returned Promise is rejected
        throw error
      }
    )
  }
}

// Thunks can be dispatched, if Redux Thunk is applied,
// just like normal action creators:
store.dispatch(getUser(42));

// The return value of dispatch() when you dispatch a thunk *is*
// the return value of the inner function. This is why it's useful
// to return a Promise (even though it is not strictly necessary):
store.dispatch(getUser(42)).then(() =>
  console.log('Fetched user and updated UI!')
)

// Here is another thunk action creator.
// It works exactly the same way.
export function getPost(id) {
  return dispatch => {
    dispatch({ type: 'GET_POST_REQUEST', id })
    return fetchPost().then(
      response => dispatch({ type: 'GET_POST_SUCCESS', id,  response }),
      error => {
        dispatch({ type: 'GET_POST_FAILURE', id,  error })
        throw error
      }
    )
  }
}

// Now we can combine them
export function getUserAndTheirFirstPost(userId) {
  // Again, Redux Thunk will inject dispatch here.
  // It also injects a second argument called getState() that lets us read the current state.
  return (dispatch, getState) => {
    // Remember I told you dispatch() can now handle thunks?
    return dispatch(getUser(userId)).then(() => {
      // Assuming this is where the fetched user got stored
      const fetchedUser = getState().usersById[userId]
      // Assuming it has a "postIDs" field:
      const firstPostID = fetchedUser.postIDs[0]
      // And we can dispatch() another thunk now!
      return dispatch(getPost(firstPostID))
    })
  }
}

// And we can now wait for the combined thunk:
store.dispatch(getUserAndTheirFirstPost(43)).then(() => {
  console.log('fetched a user and their first post')
})

// We can do this anywhere we have access to dispatch().
// For example, we can use this.props.dispatch, or put action
// creators right into the props by passing them to connect, like this:
// export default connect(mapStateToProps, { getUserAndTheirFirstPost })

Мы должны внести это в FAQ.

Все 24 Комментарий

Привет! Это система отслеживания проблем, а не форум поддержки. Мы будем признательны, если вы в следующий раз спросите на StackOverflow, потому что ответы здесь теряются, в отличие от SO.

Тем не менее, если вы создаете свой магазин с промежуточным ПО Redux Thunk , вы можете написать создателей асинхронных действий следующим образом:

// If you use Redux Thunk...
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))

// You can define asynchronous action creators that return functions.
// We call such action creators "thunks":

export function getUser(id) {
  // Redux Thunk will inject dispatch here:
  return dispatch => {
    // Reducers may handle this to set a flag like isFetching
    dispatch({ type: 'GET_USER_REQUEST', id })

    // Perform the actual API call
    return fetchUser().then(
      response => {
        // Reducers may handle this to show the data and reset isFetching
        dispatch({ type: 'GET_USER_SUCCESS', id,  response })
      },
      error => {
        // Reducers may handle this to reset isFetching
        dispatch({ type: 'GET_USER_FAILURE', id,  error })
        // Rethrow so returned Promise is rejected
        throw error
      }
    )
  }
}

// Thunks can be dispatched, if Redux Thunk is applied,
// just like normal action creators:
store.dispatch(getUser(42));

// The return value of dispatch() when you dispatch a thunk *is*
// the return value of the inner function. This is why it's useful
// to return a Promise (even though it is not strictly necessary):
store.dispatch(getUser(42)).then(() =>
  console.log('Fetched user and updated UI!')
)

// Here is another thunk action creator.
// It works exactly the same way.
export function getPost(id) {
  return dispatch => {
    dispatch({ type: 'GET_POST_REQUEST', id })
    return fetchPost().then(
      response => dispatch({ type: 'GET_POST_SUCCESS', id,  response }),
      error => {
        dispatch({ type: 'GET_POST_FAILURE', id,  error })
        throw error
      }
    )
  }
}

// Now we can combine them
export function getUserAndTheirFirstPost(userId) {
  // Again, Redux Thunk will inject dispatch here.
  // It also injects a second argument called getState() that lets us read the current state.
  return (dispatch, getState) => {
    // Remember I told you dispatch() can now handle thunks?
    return dispatch(getUser(userId)).then(() => {
      // Assuming this is where the fetched user got stored
      const fetchedUser = getState().usersById[userId]
      // Assuming it has a "postIDs" field:
      const firstPostID = fetchedUser.postIDs[0]
      // And we can dispatch() another thunk now!
      return dispatch(getPost(firstPostID))
    })
  }
}

// And we can now wait for the combined thunk:
store.dispatch(getUserAndTheirFirstPost(43)).then(() => {
  console.log('fetched a user and their first post')
})

// We can do this anywhere we have access to dispatch().
// For example, we can use this.props.dispatch, or put action
// creators right into the props by passing them to connect, like this:
// export default connect(mapStateToProps, { getUserAndTheirFirstPost })

Мы должны внести это в FAQ.

Я решил эту проблему следующим образом. Без действий по изменению. Я вставил компонент Promise.

  clickShowUserEvent(data) {
   Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
    .then(function (response) {
      data.showEvents(); //dispatch
      return response;
    })
    .then(function(response){console.log("@RESPONSE",response);data.show(data)})
  }

Это правильное решение?

Это тоже работает, какой шаблон использовать, зависит от вас.

@ ar53n Шаблон с обещанием в компоненте React имеет несколько недостатков:

  • Нет обработки ошибок (в примере выше ), т.е. части catch . Вы можете получить необработанные отказы.
  • Это непрерывно, например, когда компонент отключается или происходит какое-то действие, которое изменяет состояние приложения.
  • Его состояние неявно. Хотя побочные эффекты — это отдельная тема для обсуждения, было бы полезно, по крайней мере, иметь трассировку этого запущенного процесса в состоянии приложения.

@sompylasar спасибо, Джон, спасибо за ваши комментарии. Я просто не хочу изменять простые действия. У нас есть 2 простых действия Authentication и GetEvents это 2 асинхронных запроса и даже содержат catch . Просто этот вызов действия при нажатии на компонент
например

export function userAuth(login, password) {
  return (dispatch, getState) => {
    console.log('STATE', getState())
    let newState = dispatch(requestUserAuth(login, password))
    return fetch(AUTH_URL + newState.queryParams, MY_INIT)
      .then(response => response.json())
      .then(function (json) { dispatch(receiveUserAuth(json)); return json})
      .catch(error => dispatch(errorUserAuth(error)))
  }
}

И у нас есть это
image

Поправьте меня, если я ошибаюсь, спасибо

@ ar53n Тогда все в порядке, ошибки обрабатываются, а процесс отслеживается в магазине. Проблема с непрерывным процессом в компоненте все еще актуальна, но это, вероятно, не так важно, если вы начали в компоненте или в переходнике действия.

@ ar53n Вы также можете взглянуть на redux-dataloader .
Он предназначен для сложных цепочек асинхронных действий.
Надеюсь, поможет!

Закрытие этого, так как здесь есть несколько возможных решений. Вы также можете заглянуть в редукс-сагу в наши дни!

@gaearon , ваш пример прерывает путешествие во времени, не так ли?

Я имею в виду, например, что ваш вызов AJAX не удался в первый раз, а затем вы исправите серверную часть и хотите повторить его.

@gaearon Я попробовал ваше решение, но когда я пытаюсь вызвать store.dispatch(...) из любого компонента (в моем случае из LoginComponent для выполнения запроса на авторизацию), я получил эту ошибку:

undefined is not an object (evaluating '_AppNavigator.AppNavigator.router')

Кажется, что-то не так. Я настроил создателя действия следующим образом:

// actions.tsx
export const ActionCreators = {
    authenticate: (username: string, password: string) => {
        return (dispatch) => {
            return auth.login(username, password).then(
                response => {
                    dispatch(navActionCreators.login(res))
                    return response
                },
                error => {
                    throw error
                }
            )
        }
    }
}

// LoginScreen.tsx (login method)
store.dispatch(authActions.authenticate(this.state.username, this.state.password))
  .then((res) => {
     this.setState({isLoading: false})
   })
   .catch((error: Error) => {
     this.setState({
       isLoading: false,
       error: error ? error.message : 'Si è verificato un\' errore.'
     })
   })

Что мне не хватает?

@bm-software: этот вопрос следует задать вместо этого в Stack Overflow.

@ ar53n и @sompylasar , я знаю, что это было давно, но сейчас я борюсь с этим шаблоном. @ ar53n в вашем примере, если fetch внутри userAuth дает сбой, что происходит в цепочке, где вызывается userAuth ?

Похоже, что .catch(error => dispatch(errorUserAuth(error))) отправит действие errerUserAuth, и это здорово. В Redux обычно именно так мы «обрабатываем» ошибки. Но в цепочке, которую вы упомянули ранее:

clickShowUserEvent(data) {
   Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
    .then(function (response) {
      data.showEvents(); //dispatch
      return response;
    })
    .then(function(response){console.log("@RESPONSE",response);data.show(data)})
  }

data.showEvents() _всегда_ будет вызываться, даже если авторизация пользователя не удалась. Я не думаю, что это то, чего большинство людей ожидает или хочет, но поскольку обработка ошибок Redux обычно выполняется путем отправки, а не повторного генерирования, он проглатывает ошибки, поэтому цепочка промисов не работает должным образом.

Кроме того, если вы делаете повторный бросок, вам приходится .catch() на _каждый вызов создателя действия в вашем приложении, везде_. Пример от @gaearon выше, где он повторно выдает все ошибки, делает это в конце:

// And we can now wait for the combined thunk:
store.dispatch(getUserAndTheirFirstPost(43)).then(() => {
  console.log('fetched a user and their first post')
})

Если _все_ в большой комбинированной цепочке внутри getUserAndTheirFirstPost произойдет сбой, возникнет ошибка "Отклонение необработанного обещания".

Я думаю, что единственный ответ — перебросить, а затем .catch() везде или, возможно, использовать границы ошибок React 16.

@jasonrhodes

clickShowUserEvent(data) {
   Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
    .then(function (response) {
      data.showEvents(); //dispatch

data.showEvents() _всегда_ будет вызываться, даже если авторизация пользователя не удалась.

Нет, он не будет вызван, если data.userAuth(data.login, data.password) вернет обещание, которое в конечном итоге будет отклонено. Первый аргумент .then вызывается, когда обещание выполнено (называется onFulfilled ), второй — когда обещание отклонено (называется onRejected ) — см. спецификацию .

С другой стороны, границы ошибок React 16 не помогут с промисами, они только перехватывают синхронно генерируемые исключения, чтобы гарантировать, что внутреннее состояние React не будет нарушено.

@jasonrhodes Кроме того, будьте хорошим гражданином мира Promise, либо верните обещание вызывающей стороне (затем он должен обработать ошибки), либо прикрепите к нему .catch (и обработайте ошибки там, где обещание создано).

Ваш пример не делает ничего:

clickShowUserEvent(data) {
   Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
    .then(function (response) {
      data.showEvents(); //dispatch
      return response;
    })
    .then(function(response){console.log("@RESPONSE",response);data.show(data)})
  }

@sompylasar , это был не мой пример, это был тот, на который вы отвечали ранее в этой ветке. Я знаю, что это было давно, но я ссылался на более ранние разговоры здесь, потому что я наткнулся на эту тему через поиск Google более одного раза.

Посмотрите еще раз, data.showEvents() будет _всегда_ вызываться, даже если вызов fetch внутри функции userAuth завершится ошибкой. Почему? Потому что внутри функции userAuth есть .catch() $, которая обрабатывает ошибку способом Redux: отправляя действие ошибки.

В этом суть моего поста: когда вы ловите ошибки из асинхронных действий, чтобы вы могли отправлять действия с ошибками, вы проглатываете и предотвращаете правильную работу дальнейшей цепочки обещаний? Или вы повторно выбрасываете и заставляете каждого вызывающего абонента этого создателя действия .catch() независимо от того, что делать, чтобы избежать «отклонения необработанных обещаний»?

Кроме того, по своему опыту я обнаружил, что полезно понять, что кто-то знает, прежде чем связывать их со спецификацией. Спасибо за ответ, я знаю, что это старый разговор, который я откопал, но он важен, поскольку вы можете видеть, как легко его пропустить!

@jasonrhodes

@sompylasar , это был не мой пример, это был тот, на который вы отвечали ранее в этой ветке. Я знаю, что это было давно, но я ссылался на более ранние разговоры здесь, потому что я наткнулся на эту тему через поиск Google более одного раза.

Понял, извиняюсь, не вспомнил всего контекста.

Потому что внутри функции userAuth есть .catch() $, которая обрабатывает ошибку способом Redux: отправляя действие ошибки.

Понятно, тогда либо это работает как задумано, либо вам не следует помещать туда .catch , если вы вернете это обещание (и обработаете ошибки на сайтах вызовов), либо вы должны повторно выдать ошибку в .catch Обработчик

В любом случае, сами преобразователи не очень подходят для создания цепочек. Вы должны связать внутри преобразователь или использовать саги для более сложных асинхронных рабочих процессов.

Я застрял с этой проблемой разрыва цепочки обещаний на некоторое время. Обычно мои создатели действий выдают действие SUCCESS в .then() или действие FAILURE в .catch() того же http-запроса, который возвращается из thunk.

Всякий раз, когда мой создатель действия переходил к блоку catch и я делал this.props.myActionCreator().then(() => ) , код внутри then выполнялся, даже если в запросе были проблемы.

Чтобы учесть это, я всегда проверял наличие переменной ошибки в состоянии приложения, которое установлено в случае FAILURE для этого создателя действия. Но все запуталось бы, если бы вы звонили нескольким создателям действий, особенно тем, которые зависели друг от друга. У меня должен был быть оператор if, проверяющий множество переменных ошибок.

Мне нравится тот факт, что цепочка обещаний не прерывается путем повторной выдачи ошибки в блоке catch для возвращаемого значения создателя действия. Однако это потребует от нас использования .catch() из компонента React, где вызывается создатель действия. Я бы ничего не написал в этом блоке catch, поскольку переменная ошибки уже установлена ​​обработкой действия FAILURE в редюсере.

Итак, вы, ребята, @jasonrhodes , @sompylasar , порекомендовали бы мне использовать подход с повторным броском и размещением пустого блока .catch() в цепочке обещаний вызовов создателя действий в компоненте React?

@nbkhope , если честно, это была моя самая большая проблема с Redux, и до сих пор я не нашел хорошего ответа. Извините, что не могу быть более полезным!

Ребята, вы можете найти несколько альтернатив преобразователь в этой статье https://decembersoft.com/posts/what-is-the-right-way-to-do-asynchronous-operations-in-redux/

@gaearon

Что касается вашего первого ответа, как мне его преобразовать, если я использую async & await вместо обещаний? Что-то вроде следующего:

export const funcA = () => {
    return async (dispatch) => {
        const data = await doSomething(...)
        dispatch({ action: DID_SOMETHING, payload: data })
    }
}

export const funcB = () => {
    return async (dispatch) => {
        const data = await doSomethingElse(...)
        dispatch({ action: DID_SOMETHING_ELSE, payload: data })
    }
}

export const funcC = () => {
    return async (dispatch) => {
        const data = await doSomethingMore(...)
        dispatch({ action: DID_SOMETHING_MORE, payload: data })
    }
}

// how to chain funcA, funcB and funcC
const myFunc = () => {
    // execute funcA
    // when complete execute funcB
    // when complete execute funcC
}

@yingdongzhang вы можете связать их следующим образом:

const myFunc = () => {
  return async (dispatch) => {
    try {
      await dispatch(funcA())
      await dispatch(funcB())
      await dispatch(funcC())
    } catch (error) {
      //error handling
    }
  }
}

@Boomxx Спасибо, работает, как и ожидалось.

Мне интересно, как бы я сделал fetch all posts of the user .

@ km16 : Это система отслеживания ошибок, а не система поддержки. Для вопросов по использованию, пожалуйста, используйте Stack Overflow или Reactiflux, где есть гораздо больше людей, готовых помочь вам - вы, вероятно, быстрее получите лучший ответ. Спасибо!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги