Здравствуйте, изучаю 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.
Я решил эту проблему следующим образом. Без действий по изменению. Я вставил компонент 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)))
}
}
И у нас есть это
Поправьте меня, если я ошибаюсь, спасибо
@ 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, где есть гораздо больше людей, готовых помочь вам - вы, вероятно, быстрее получите лучший ответ. Спасибо!
Самый полезный комментарий
Привет! Это система отслеживания проблем, а не форум поддержки. Мы будем признательны, если вы в следующий раз спросите на StackOverflow, потому что ответы здесь теряются, в отличие от SO.
Тем не менее, если вы создаете свой магазин с промежуточным ПО Redux Thunk , вы можете написать создателей асинхронных действий следующим образом:
Мы должны внести это в FAQ.