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

Comments

@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

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Sep 13, 2015

Collaborator

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!

Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@coodoo

coodoo Sep 13, 2015

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

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

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Sep 13, 2015

Collaborator

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

Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@coodoo

coodoo Sep 13, 2015

Understood, thanks!

coodoo commented Sep 13, 2015

Understood, thanks!

@koresar

This comment has been minimized.

Show comment
Hide comment
@koresar

koresar commented Sep 14, 2015

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

@sureshvarman

This comment has been minimized.

Show comment
Hide comment
@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

This comment has been minimized.

Show comment
Hide comment
@dylanpyle

dylanpyle 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?

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

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Feb 13, 2016

Collaborator

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.

Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@dylanpyle

dylanpyle Feb 13, 2016

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

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

This comment has been minimized.

Show comment
Hide comment
@mocheng

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

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

This comment has been minimized.

Show comment
Hide comment
@markerikson

markerikson Jun 2, 2016

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.

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@mocheng

mocheng 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().

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

This comment has been minimized.

Show comment
Hide comment
@markerikson

markerikson Jun 2, 2016

Contributor

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

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@timothytong

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

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

This comment has been minimized.

Show comment
Hide comment
@johanneslumpe

johanneslumpe Jun 30, 2016

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.

Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@timothytong

timothytong 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?

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

This comment has been minimized.

Show comment
Hide comment
@Sailias

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

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

This comment has been minimized.

Show comment
Hide comment
@jintoppy

jintoppy 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?

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

This comment has been minimized.

Show comment
Hide comment
@6zz

6zz 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.

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

This comment has been minimized.

Show comment
Hide comment
@apranovich

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

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

This comment has been minimized.

Show comment
Hide comment
@jintoppy

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

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

This comment has been minimized.

Show comment
Hide comment
@JorgeCeja

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

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

This comment has been minimized.

Show comment
Hide comment
@aayushis12

aayushis12 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 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

This comment has been minimized.

Show comment
Hide comment
@aayushis12

aayushis12 Oct 3, 2017

@c0b41 , please check the updated question.

aayushis12 commented Oct 3, 2017

@c0b41 , please check the updated question.

@aayushis12

This comment has been minimized.

Show comment
Hide comment
@aayushis12

aayushis12 Oct 3, 2017

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

aayushis12 commented Oct 3, 2017

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

@markerikson

This comment has been minimized.

Show comment
Hide comment
@markerikson

markerikson Oct 3, 2017

Contributor

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

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@HSpens

HSpens 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 =>.

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

This comment has been minimized.

Show comment
Hide comment
@enmanuelduran

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

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

This comment has been minimized.

Show comment
Hide comment
@markerikson

markerikson Aug 14, 2018

Contributor

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

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@enmanuelduran

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

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