Redux: How to chain async actions?

Created on 28 Apr 2016  ·  24Comments  ·  Source: reduxjs/redux

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?

docs

Most helpful comment

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.

All 24 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.

  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)})
  }

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:

  • There is no error handling (in the example above), i.e. the catch part. You may get unhandled rejections.
  • It's uninterruptible, e.g. when the component unmounts or some action happens that changes the app state.
  • Its state is implicit. Though the side-effects is a separate discussion, at least having a trace of that running process in the app state would be useful.

@sompylasar thank you John thanks for your comments. I just dont want modify simple actions. We have 2 simple actions Authenticationand GetEventsit is 2 async request and even contain catch. Just this action call when click on component
e.g.

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)))
  }
}

And we have this
image

С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.
It is designed for complex chain async actions.
Hope it helps!

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 store.dispatch(...) from any component (in my case from LoginComponent to do an authorize request) I got this error:

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

Seems that something is wrong. I've set up the action creator like this:

// 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.'
     })
   })

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 fetch inside userAuth fails, what happens in the chain where userAuth is called?

It looks like .catch(error => dispatch(errorUserAuth(error))) would dispatch the errerUserAuth action, which is great. In Redux this is typically how we "handle" errors. But in the chain you mentioned earlier:

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() is _always_ going to be called, even if the user auth fails. I don't think that's what most people would expect or want, but since Redux error-handling is usually done by dispatching and not re-throwing, it swallows errors so that promise chaining doesn't work as expected.

Also, if you do re-throw, you're stuck having to .catch() on _every single action creator call in your app, everywhere_. The example from @gaearon above, where he re-throws all errors, does this at the end:

// 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 getUserAndTheirFirstPost fails, there would be an "Unhandled promise rejection" error.

I think the only answer is to re-throw and then .catch() everywhere, or possibly to use React 16 error boundaries.

@jasonrhodes

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

data.showEvents() is _always_ going to be called, even if the user auth fails.

No, it won't be called if data.userAuth(data.login, data.password) returns a promise which eventually gets rejected. The first argument to .then is called when the promise is fulfilled (it's called onFulfilled), the second one when the promise is rejected (it's called onRejected) – see the spec.

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 .catch to it (and handle errors where the promise is created).

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, data.showEvents() will _always_ be called even if the fetch call inside the userAuth function fails. Why? Because there is a .catch() inside the userAuth function that handles the error in the Redux way: by dispatching an error action.

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 .catch() no matter what to avoid "Unhandled promise rejections"?

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!

@jasonrhodes

@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.

Got it, I apologize, haven't recalled the whole context.

Because there is a .catch() inside the userAuth function that handles the error in the Redux way: by dispatching an error action.

Got it, then either this works as intended, or you shouldn't put .catch there if you return that promise (and handle errors at the call sites), or you should re-throw the error within the .catch handler to reject the downstream promise.

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 this.props.myActionCreator().then(() => ), the code inside the then would execute even if there were problems in the request.

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!

@gaearon

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:

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

@Boomxx Thank you works as expected.

I am wondering how would I do a fetch all posts of the user.

@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!

Was this page helpful?
0 / 5 - 0 ratings