whats the redux idiom for waiting for multiple async calls? #723

Closed
pdeva opened this Issue Sep 13, 2015 · 17 comments

Projects

None yet
@pdeva
pdeva commented Sep 13, 2015

When i initiate react component, I need to make 2 different async http calls and wait for their results.
These 2 calls will ideally put their data in 2 different reducers.

I understand the pattern for making a single async call.
However, I dont see any good samples for making multiple async calls, having them execute in parallel and wait for both their results to arrive.

What is the redux way of doing this?

@pdeva pdeva changed the title from whats the idiom for this in reflux to whats the redux idiom for waiting for multiple async calls? Sep 13, 2015
@gaearon
Member
gaearon commented Sep 13, 2015

I'm assuming you use Redux Thunk middleware.

Declare some action creators:

import 'babel-core/polyfill'; // so I can use Promises
import fetch from 'isomorphic-fetch'; // so I can use fetch()

function doSomething() {
  return dispatch => 
    fetch(
      '/api/something'
    ).then(
      response => response.json()
    ).then(
      json => dispatch({ type: DO_SOMETHING, json }),
      err => dispatch({ type: SOMETHING_FAILED, err })
    );
}

function doSomethingElse() {
  return dispatch => 
    fetch(
      '/api/something'
    ).then(
      response => response.json()
    ).then(
      json => dispatch({ type: DO_SOMETHING_ELSE, json }),
      err => dispatch({ type: SOMETHING_ELSE_FAILED, err })
    );
}

Note how I make it a point to return a Promise at the very end by keeping the chain as return value of the function inside thunk action creators. This lets me do that:

store.dispatch(doSomething()).then(() => {
  console.log('I did something');
});

When you use Redux Thunk, dispatch returns the return value of the function you returned from the thunk action creator—in this case, the Promise.

This lets you use combinators like Promise.all() to wait for completion of several actions:

Promise.all([
  store.dispatch(doSomething()),
  store.dispatch(doSomethingElse())
]).then(() => {
  console.log('I did everything!');
});

However it is preferable to define another action creator that would act as a composition of the previous action creators, and use Promise.all internally:

function doEverything() {
  return dispatch => Promise.all([
    dispatch(doSomething()),
    dispatch(doSomethingElse())
  ]);
}

Now you can just write

store.dispatch(doEverything()).then(() => {
  console.log('I did everything!');
});

Happy dispatching!

@gaearon gaearon closed this Sep 13, 2015
@gaearon gaearon added the question label Sep 13, 2015
@coodoo
coodoo commented Sep 13, 2015

@gaearon Is there any particular reason that you favor thunk over promise middleware? or advice on when to use what? Thanks.

@gaearon
Member
gaearon commented Sep 13, 2015

No particular reason, I'm just more familiar with it.
Try both and use what works best for you!

@coodoo
coodoo commented Sep 13, 2015

Understood, thanks!

@koresar
koresar commented Sep 14, 2015

JFYI :)
screen shot 2015-09-14 at 11 06 42 am

@sureshvarman

if you want you can take a look on it https://github.com/Cron-J/redux-async-transitions

@dylanpyle

@gaearon quick question - looking at your example above, where you call:

store.dispatch(doSomething()).then(() => {
  console.log('I did something');
});

— it looks like that "I did something" block would be hit even if doSomething rejected - since you're not rethrowing the error in the doSomething chain after you catch it. Is that intentional? If not - and if you'd rethrow the error there instead - would you handle it again in the caller?

@gaearon
Member
gaearon commented Feb 13, 2016

It is just an example, I don’t mean this a definitive reference, just as a way to look at it. I didn’t give it a minute of real thought.

You know how to return promises from action creators, the rest is up to you. Whether to rethrow errors, where to handle them, is all up to you. This is not really a Redux concern so my advise is as good as anyone else’s, and in this case you have much more context on what you want build and the style you prefer than I will ever have.

@dylanpyle

Cool, thanks! Figured that was the case, but wanted to make sure there wasn't some magic I was overlooking.

@mocheng
mocheng commented Jun 2, 2016

Since Redux is customizable, dispatch return value is not guaranteed to be expected promise.

Actually, I ran into one issue in real life since I takes redux-loop. It tweaks dispatch to return a wrapped Promise(https://github.com/raisemarketplace/redux-loop/blob/master/modules/effects.js#L38). So, we cannot depend on return value of dispatch.

@markerikson
Contributor

Well, as Dan said above in this thread: it's your app, you are writing your action creators, so you get to decide what they return.

@mocheng
mocheng commented Jun 2, 2016

@markerikson The return value of dispatch is not guaranteed to be return value of action creator, since middleware may tweak it.

But, I got a workaround. Instead of chain then on dispatch(actonCreator()), we can chain on actionCreator().

@markerikson
Contributor

Yes, middleware can tweak things, but my point is that you are setting up the middleware in your own app :)

@timothytong
timothytong commented Jun 30, 2016 edited

Hi @gaearon, quick (hopefully related) question! What are your views on doing something like

function fetchDataBWhichDependsOnA() {
  return dispatch => {
    dispatch(fetchDataA())
      .then(() => {
        fetch('/api/get/data/B')
          .then(dispatch(receiveDataB(response.data)))
      })
  }
}

There are 2 nested async actions in this code snippet, they work but I'm having trouble testing it. I'm able to test fetchDataA() because it has only 1 level of asynchronous call. But when I was trying to write a test for fetchDataBWhichDependsOnA(), I couldn't retrieve the most updated values from the store in then block.

@johanneslumpe
Collaborator

@timothytong You are not returning the promises. If you return the result from dispatch and the nested fetch call you should be able to wait in your tests until the whole chain has been resolved.

@timothytong

@johanneslumpe good catch, thanks mate! Now all that's left is the question of whether this is acceptable or not, dealing with data dependency with an async fetch chain?

@Sailias
Sailias commented Jul 19, 2016 edited

I had the same problem and spent 30 minutes trying to come up with a solution. I'm not done this implementation, but I feel like it's on the right track.

https://github.com/Sailias/sync-thunk

My idea is that a component can request certain redux states to be populated, if they are not it should refer to a map to call the actions to populate them and chain them with then. Each action in the chain doesn't need data passed in, it can just take the data from the state as the dependant actions would have populated it.

@matcornic matcornic referenced this issue in soprasteria/docktor Dec 22, 2016
Open

Handle multiple async calls #48

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment