Skip to content
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

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

Closed
pdeva opened this issue Sep 13, 2015 · 30 comments
Closed

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

pdeva opened this issue Sep 13, 2015 · 30 comments
Labels

Comments

@pdeva
Copy link

@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 whats the idiom for this in reflux whats the redux idiom for waiting for multiple async calls? Sep 13, 2015
@gaearon
Copy link
Contributor

@gaearon 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
@coodoo
Copy link

@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
Copy link
Contributor

@gaearon 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
Copy link

@coodoo coodoo commented Sep 13, 2015

Understood, thanks!

@koresar
Copy link

@koresar koresar commented Sep 14, 2015

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

@sureshvarman
Copy link

@sureshvarman sureshvarman commented Nov 23, 2015

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

@dylanpyle
Copy link

@dylanpyle dylanpyle commented Feb 13, 2016

@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
Copy link
Contributor

@gaearon 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
Copy link

@dylanpyle dylanpyle commented Feb 13, 2016

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

@mocheng
Copy link

@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
Copy link
Contributor

@markerikson markerikson commented Jun 2, 2016

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
Copy link

@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
Copy link
Contributor

@markerikson markerikson commented Jun 2, 2016

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

@timothytong
Copy link

@timothytong timothytong commented Jun 30, 2016

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
Copy link
Contributor

@johanneslumpe johanneslumpe commented Jun 30, 2016

@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
Copy link

@timothytong timothytong commented Jun 30, 2016

@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
Copy link

@Sailias Sailias commented Jul 19, 2016

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.

@jintoppy
Copy link

@jintoppy jintoppy commented Mar 31, 2017

@gaearon

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

I have a question on this. Sorry if it is so basic.

In this scenario, there will be two dispatches. That means, two times the component will be rendered. But, what if component wants both data to show something meaningful?

So, in that scenario, I would like the component to render only once and that is when all the async actions are completed.

I got names of few middlewares which seem to be handling this issue like redux-batched-subscribe and redux-batched-actions. But, a little confused on the best approach.

If this is a valid scenario, what would be the suggested approach to handle this?

@6zz
Copy link

@6zz 6zz commented Apr 25, 2017

@jintoppy if your ask is to avoid multiple call to render a component then the Promise.all solution will not help you; because both doSomething() and doSomething() all dispatch data that would trigger state change and cause component render.

@apranovich
Copy link

@apranovich apranovich commented May 2, 2017

@jintoppy why do you need any middlwares to handle your situation?
What's hinder you of doing smth like that.

// action creator
function fetchAB() {
  return dispatch => {
    const fetchA = fetch( 'api/endpoint/A' );
    const fetchB = fetch( 'api/endpoint/B' );
    return Promise.all([ fetchA, fetchB ])
      .then( values => dispatch(showABAction(values)) )
      .catch( err => throw err );
  }
} 

You can just put all your request into the one action creator and fire dispatching of action only once all of them are resolved. In that case you have reducer called and thus state changed only once, so render() likely will be fired only once as well.

@jintoppy
Copy link

@jintoppy jintoppy commented May 3, 2017

@apranovich: I see one problem with this approach. Now, I need to combine multiple fetch in a single action. So, if I have, let's say 10 async calls, I have to put all those in a single action. Also, what if I want to trigger one out of these 10 async calls later? In this approach, basically, all your async logic will lie in a single action.

@JorgeCeja
Copy link

@JorgeCeja JorgeCeja commented Sep 10, 2017

Thank you all for the helpful information. Quick question, how can the same be achieved but instead when one fetch returns data that is required by the next fetch method. For example, there is a free weather API endpoint where I input the geolocation to get an id (first fetch) which is required for the next fetch to get the location's weather. I know this is an odd use case, but I would like to know how to handle similar situations. Thanks in advance!

@aayushis12
Copy link

@aayushis12 aayushis12 commented Oct 3, 2017

Hi @gaearon, I would like to get your review on this question of mine. I have to make nested async calls to two APIs. The response from the second API is dependent on the response of the first one.

export const getApi=()=>{
		return(d)=>{
		axios({
			method:'GET',
			url:Constants.URLConst+"/UserProfile",
			headers:Constants.headers
		}).then((response)=>{
			return d({
				type:GET_API_DATA,
				response,
				axios({
					method:'GET',
					url:Constants.URLConst+"/UserProfileImage?enterpriseId="+response.data.ProfileData.EnterpriseId,
					headers:Constants.headers
				}).then((response1)=>{
					return d({
						type:GET_PROFILE_IMAGE,
						response1
					})
				})
			})
		}).catch((e)=>{
			console.log("e",e);
		})
	}

}

I tried something like above, but this doesn't work.And in my project, I have to use response from both the APIs independently.
So, what is the right way of doing it?

Thanks.

@aayushis12
Copy link

@aayushis12 aayushis12 commented Oct 3, 2017

@c0b41 , please check the updated question.

@aayushis12
Copy link

@aayushis12 aayushis12 commented Oct 3, 2017

hey @c0b41 , this is giving syntax errors in the second axios call.

@markerikson
Copy link
Contributor

@markerikson markerikson commented Oct 3, 2017

@aayushis12 , @c0b41 : this is a bug tracker, not a help forum. You should try asking this question on Stack Overflow or Reactiflux. There will be more people who will see the question and be able to help.

@hspens
Copy link

@hspens hspens commented Aug 14, 2018

For those experiencing that

I did everything

is printed before all async calls are resolved, check whether you're returning a statement or an expression from your async action creators.

For example, if doSomethingElse() is declared with { } in the arrow function

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

"I did everything" will print before doSomethingElse() resolves. Solve this by dropping { } after =>.

@enmanuelduran
Copy link

@enmanuelduran enmanuelduran commented Aug 14, 2018

@gaearon one question, based on #723 (comment) how would you handle it if you need to dispatch an action that waits for two actions to have been dispatched AND the state to have changed because of those actions? I'm having a case in which the actions are dispatched but the action inside the then is executed before the state has changed. We're using redux-thunk.

@markerikson
Copy link
Contributor

@markerikson markerikson commented Aug 14, 2018

@enmanuelduran : Dan isn't an active maintainer any more - please don't ping him.

It's hard to answer your question without seeing code, but our issues really aren't intended to be a discussion forum. It would be better if you post your question on Stack Overflow - you're likely to get a better answer there, faster.

@enmanuelduran
Copy link

@enmanuelduran enmanuelduran commented Aug 14, 2018

@markerikson oh, sorry, it was not my intention, created a question in stackoverflow if someone is interested: https://stackoverflow.com/questions/51846612/dispatch-an-action-after-other-specific-actions-were-dispatched-and-state-change

thanks a lot guys.

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

Successfully merging a pull request may close this issue.

None yet