-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to chain async actions? #1676
Comments
Hi! This is an issue tracker and not a support forum. We’d appreciate you asking on StackOverflow the next time because the answers here get lost, unlike on SO. That said, if you create your store with Redux Thunk middleware, you can write async action creators like this: // 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 }) We should put this into the FAQ. |
I solved this problem as follows. Without modify actions. I put in the component Promise.
This is the correct decision? |
This works too, which pattern to use is up to you. |
@ar53n The pattern with a promise in a React component has several flaws:
|
@sompylasar thank you John thanks for your comments. I just dont want modify simple actions. We have 2 simple actions
Сorrect me if I'm wrong, thank you |
@ar53n Then you're good, the errors are handled, and the process is tracked in the store. The issue with uninterruptible process in a component still applies, but that's probably not that important if you have started in a component or in an action thunk. |
@ar53n You may also take a look at redux-dataloader. |
Closing this out since there are some possible solutions posted here. You may also want to look into redux-saga nowadays! |
@gaearon does your example break time traveling isn't it ? I mean for instance your AJAX call failed first time and then you go fix server side and want to redo it |
@gaearon I tried your solution but when I try to call
Seems that something is wrong. I've set up the action creator like this:
What am I missing? |
@bm-software: That question should be asked on Stack Overflow instead. |
@ar53n and @sompylasar it's been a while I know, but I'm struggling with this pattern right now. @ar53n in your example, if the It looks like 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)})
}
Also, if you do re-throw, you're stuck having to // And we can now wait for the combined thunk:
store.dispatch(getUserAndTheirFirstPost(43)).then(() => {
console.log('fetched a user and their first post')
}) If anything in the big combined chain inside I think the only answer is to re-throw and then |
clickShowUserEvent(data) {
Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
.then(function (response) {
data.showEvents(); //dispatch
No, it won't be called if On the other note, React 16 error boundaries won't help with promises, they only catch synchronously thrown exceptions to ensure that the React internal state is not broken. |
@jasonrhodes Also, be a good citizen of the Promise world, either return the promise to the caller (it must then handle the errors) or attach a Your example does none: 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 it wasn't my example, it was one you had been responding to earlier in this thread. I know it's been a while, but I was referencing earlier conversations here because I've come across this thread via Google searches more than once. Look again, This is the point of my post: when you catch errors from async actions so you can dispatch error actions, do you swallow and prevent further promise chaining from working correctly? Or do you re-throw and force every caller of that action creator to Also, in my experience I've found it's a good idea to understand what someone knows before linking them to a spec. Thanks for responding, I know it's an old conversation I've dug up, but it's an important one as you can see how easy it is to miss! |
Got it, I apologize, haven't recalled the whole context.
Got it, then either this works as intended, or you shouldn't put Anyway, thunks themselves aren't well suited for chaining. You should chain inside a thunk, or use sagas for more complex asynchronous workflows. |
I have been stuck with that problem of breaking the promise chain for a while. Usually, my action creators would emit a SUCCESS action in the .then() or a FAILURE action in the .catch() of that same http request that is returned from the thunk. Whenever my action creator went to the catch block and I did To account for that, I always made sure to check for an error variable in the app state that is set in the FAILURE case for that action creator. But things would get messy when you called multiple action creators, especially ones that depended upon the other. I had to have an if statement checking for many error variables. I like the fact of not breaking the promise chain by re-throwing the error in the catch block for the action creator return value. However, that would require us use .catch() from the React component, where the action creator is called. I would have nothing written in that catch block, since the error variable is already set by the handling of the FAILURE action in a reducer. So would you guys, @jasonrhodes, @sompylasar, recommend me use the re-throwing approach and placing an empty .catch() block on the promise chain of action creator calls in a React component? |
@nbkhope to be honest this has been my biggest problem with Redux and to this day I have not figured out a good answer. Sorry to not be more helpful! |
Guys, you can find some thunk alternatives in this article https://decembersoft.com/posts/what-is-the-right-way-to-do-asynchronous-operations-in-redux/ |
In regards to your first answer, how should I convert it if I'm using async & await instead of promises? Something like the following: 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 you can chain them as follows:
|
@Boomxx Thank you works as expected. |
I am wondering how would I do a |
@km16 : This is a bug tracker, not a support system. For usage questions, please use Stack Overflow or Reactiflux where there are a lot more people ready to help you out - you'll probably get a better answer faster. Thanks! |
Hello, I have been studying Redux and faced with an interesting problem? need to make a chain of asynchronous requests from other Actions
1-getUser()
2-getPost()
I have 2 solution execute after sign in user .then(dispatch({type:GET_POST_REQUEST}))
or write function in middleWare.
How it to do correctly?
The text was updated successfully, but these errors were encountered: