Promise chaining to a possibly resolved promise - javascript

I have an application which, depending on the state of the form, on submit, may have to make 0-3 API calls. 1 of those calls is dependent on what we can call the root call, such that if the root call is still resolving then we would need to wait for it to finish and return that data from the API before calling the dependent call. So:
Root call
Dependent call
Call 3
This is a React/Redux project. I could track which calls are in the process of "submitting" in a redux store and if the root call is "submitting" I could setup a setInterval to check if it's resolved before firing off the dependent call. Since this file is outside of a react component, to implement the aforementioned method, I would have to import the store and depend on the store.getState() method to check the state of the store which may have just been changed in a previous if block when the action called within it sends an update to the reducer. Would calling getState directly after firing off an async action reflect the correctly updated state?
As an alternative, what if I stored the most recent call in a global variable in the api file, and then naively chained a callback to it, like this:
api.js
let currentRootCall = Promise.resolve(null);
function setCurrentRootCall(call) {
currentRootCall = call;
}
export default function submit(values){
...
if (willSubmitRootCall) {
rootCall = doRootCall(values);
setCurrentRootCall(rootCall);
} else {
rootCall = currentRootCall;
}
...
if (willSubmitDependentCall) {
rootCall.then(() => doDependentCall(values));
}
if (willSubmit3rdCall) {
thirdCall = do3rdCall(values);
} else {
thirdCall = Promise.resolve(null);
}
...
return Promise.allSettled([rootCall, thirdCall])
.then(...) // fire off call completed actions for each of the 3 calls in the redux stores
}
This would save me the headache of polling the redux store to see if the call completed and then having to clean up the interval. It is my understanding that chaining to a resolved promise will just immediately fire off the chained callback. I feel as though I'm missing something here.
4 questions:
Is there a problem with this Promise chaining approach?
Will naively chaining to a possibly already resolved Promise cause
problems? How about with Promise.allSettled?
In the polling redux store method, would calling getState on the store directly after calling an async action reflect the updated state?
Is there a better way?
FYI: I am using axios library to make these calls.

Related

How do I use yield inside a callback on redux-saga?

I have a callback which I need to return true or false to trigger some external action, I need to make an API call inside this callback, so I need to get the state and dispatch some actions inside the callback, I don't know if I can use eventChannel because this callback could not be a generator, only a plain function. I need to do something like this.
zafClient.on('ticket.save', () => {
const state = yield select();
yield put(actionWithApiCall())
// I need to wait for the action to finish and then return something
// based on the response from the API
// I know how to block the saga to wait for the action dispatched
// the problem is that I can't use yield here
return somethingfromthestore;
});
Btw, this is zendesk API.
Your not going to be able to pass a generator function to that API. The work around is to dispatch an action directly to the redux store and then write a saga that listens for that action.
zafClient.on('ticket.save', () => reduxStore.dispatch(actionWithApiCall()))
You will have to make the redux store exportable from where you create it. So that you can directly access it here.
One of the challenges is how to yield in the callback. How about importing dispatch? Isn't dispatch the non-generator version of yield?
Should you be using React, doing so seems to be an option.
In the same saga that you use to specify the callback, you can subsequently listen for the action created in the dispatch/action creator callback. That's done by specifying another watchFor function (in my setup, any number of exported watchFor functions get added to the pool of saga watchers).
The paired saga worker can finalize whatever needs to happen to the data returned from the API call before using your final action creator to "document" the workflow by updating the store.
The other option might be to wrap the api call in an eventChannel (see: https://github.com/redux-saga/redux-saga/issues/1178).
- E

React SetState Race Condition using Promise.all

componentDidMount() {
Promise.all([OfferCreationActions.resetOffer()]).then( () => {
OfferCreationActions.updateOfferCreation('viewOnly', true);
OfferCreationActions.updateOfferCreation('loadingOffer', true);
Promise.all([
OfferCreationActions.loadBarcodes(),
OfferCreationActions.loadBrandBarcodes(),
OfferCreationActions.loadBrands(),
OfferCreationActions.loadPayers(),
OfferCreationActions.loadSegments(),
OfferCreationActions.loadTactics(),
OfferCreationActions.loadStates(),
]).then(() => {
// let state settle before loading
setTimeout(() => {
OfferCreationActions.loadOffer(this.props.match.params.offerId);
}, 1500);
});
});
}
I'm working on a React app that needs to preload some data into state then load a larger object that references the preloaded data to map some fields. I've ran into a race condition in which some of the data from the promise chain is still being processed when I try to do the mapping. I added in the timeout yesterday but that doesn't feel like the best solution to me. I'm still fairly new to React and we are using Reflux as the store (if that makes a difference). Is there a better way to ensure all the data from the promise is currently being reflected in the state prior to making the call? Should I hook into componentShouldUpdate and check all of the fields individually?
There is a fundamental flaw with the way this is implemented! You are breaking the principle of uni directional data flow. Here are a few suggestions to fix it.
Do your side effect handling inside a seperate overarching function.
Handling promise race condition is a side effect(Something that is outside of React's UniFlow). So this is not a problem linked to "React". So as the first step onComponentDidMount delegate this race condition logic to a seperate action. Possibly do it inside "resetOfferOverall()" which is actually what is happening I guess.
Manage the promise inside the action and dispatch payloads to the store
In your code you are guaranteed that the "then" will be executed after the promise is resolved. Howerver the 2 calls to "updateOfferCreation" do not fall under this contract since it's outside the promise.all. May be they also need to come inside the massive promise.all section? Maybe they need to be completed before running the massive section. Just recheck this!
resetOfferOverall() {
Promise.all([OfferCreationActions.resetOffer()]).then( () => {
.then( () => {
// These are not guaranteed to be completed before the next "then" section!
OfferCreationActions.updateOfferCreation('viewOnly', true);
OfferCreationActions.updateOfferCreation('loadingOffer', true);
//*****************************************
Promise.all([
OfferCreationActions.loadBarcodes(),
OfferCreationActions.loadBrandBarcodes(),
OfferCreationActions.loadBrands(),
OfferCreationActions.loadPayers(),
OfferCreationActions.loadSegments(),
OfferCreationActions.loadTactics(),
OfferCreationActions.loadStates(),
]).then(() => {
OfferCreationActions.loadOffer(offerId);
});
});
}
If you want this sections to be completed before getting into that
massive promise all, change your code as follows.
async resetOfferOverall() {
Promise.all([OfferCreationActions.resetOffer()]).then( () => {
.then( () => {
await OfferCreationActions.updateOfferCreation('viewOnly', true);
await OfferCreationActions.updateOfferCreation('loadingOffer', true);
//await will stop code execution until the above async code is completed
Promise.all([
OfferCreationActions.loadBarcodes(),
OfferCreationActions.loadBrandBarcodes(),
OfferCreationActions.loadBrands(),
OfferCreationActions.loadPayers(),
OfferCreationActions.loadSegments(),
OfferCreationActions.loadTactics(),
OfferCreationActions.loadStates(),
]).then(() => {
//Now JS Guarantees that this call will not be called until everything above has been resolved!
OfferCreationActions.loadOffer(offerId);
});
});
}
Make sure that the actions you are waiting are returning a promise
Whatever pomises you wait on, if you do not actually return the relevant promise that is within the call itself, your code will not work properly.Let's consider the load barcodes action and Let's assume you use axios to fetch data.
loadBarcodes(){
// This "return" right here is vital to get your promises to behave properly
return axios.get('https://localhost:8080/api/barcodes/').then((response) =>{
//DISPATCH_TO_STORE
});
//If you did not return this promise this call will resolve immediately
}
On your component watch for the relevent Store. Show a loader until the payload is loaded to the store.
As you can see by relying on a store update to show the data we do not break the unidirectional data flow.

How to fix logging issues when setting state in componentWillMount

I am having issue with state as i'm not 100% i'm using componentDidMount and componentWillMount correctly.
I have set the constructor and super props, and I am getting a user object with the getCurrentUser() method and setting the user state with the new object.
componentWillMount() {
const current_user = getCurrentUser().then(user => {
this.setState({
user: user
});
console.log(user);
});
}
componentDidMount() {
console.log(this.state.user);
}
It logs the user correctly in componentWillMount, but logs an empty object in componentDidMount.
Any guidance would be massively appreciated!
Simply don't use componentWillMount,
Do it in componentDidMount.
In practice, componentDidMount is the best place to put calls to fetch data, for two reasons:
Using DidMount makes it clear that data won’t be loaded until after the initial render. This reminds you to set up initial state properly, so you don’t end up with undefined state that causes errors.
If you ever need to render your app on the server (SSR/isomorphic/other buzzwords), componentWillMount will actually be called twice – once on the server, and again on the client – which is probably not what you want. Putting the data loading code in componentDidMount will ensure that data is only fetched from the client.
getCurrentUser is an asynchronous method which calls another asynchronous method (setState).
I am pretty sure that you will first see the log entry from componentDidMount and only afterwards the log entry from componentWillMount.
What's happening is:
React calls componentWillMount
You start an async call (getCurrentUser)
componentWillMount returns immediatelly, without waiting for the promise to complete
React calls componentDidMount
Your promise resolves
The logs are due to the asynchronous nature of your method getCurrentUser. When you call getCurrentUser in componentWillMount, it might result in an output after the componentDidMount has finished executing and hence you see the initial state in componentDidMount. However the console.log in componentWillMount is in the getCurrentUser .then promise callback which will log the current value received from getCurrentUser()

Mobx State Tree Flow. How to Know when it's done?

I am using mobx state tree and mobx for UI Stuff.
Now when I save something to db, after the request is done I want to update the ui(ie my mobx state).
I need to know when the flow is finished.
myFlow: flow(function* () {
// do stuff here.
}),
now I see that a promise is returned, so I thought of just doing
myFlow.then()
which works but I am wondering if this is the property way or if there is another way to do this(async/await? or some internal thing that flow has?)
a flow returns a promise, so any promise waiting mechanism works: .then, await, or yield inside another flow. If you want to render the state of the flow, take a look at mobxUtils.fromPromise(promise).case(....) of the mobx-utils package
Inside generator you can call some another action at the end.
In example below I call thisIsWhatYouNeed function. This function will be called when generator ends.
myFlow: flow(function* () {
try {
const response = yield fetch('your URL here');
const data = yield response.json()
// here you can call another action
self.thisIsWhatYouNeed(data);
} catch (error) {
console.log('error happens');
}
})
thisIsWhatYouNeed(data) {
// here you have your data and know that flow is finished
console.log('generator already finished');
}

Where should I Compose Complex Asynchronous Flows in Redux?

I want to model the following async logic using redux:
User action triggers a chain of async API calls.
Any API call might return 401 status (login timed out)
If API responds with 401, display re-login popup
On successful re-login, reissue API call and continue
I am not sure where to put this logic. Actions don't know about other actions, they only have access to dispatch, so they can't stop and wait for them to complete. Reducers don't have access to dispatch, so I can't put it there… so where does it live? Custom middleware? store.listen? In a smart component?
I'm currently using redux-promise-middleware & redux-thunk. How would one best organise this type of flow – without requiring buy-in into something like redux-saga or redux-rx, etc?
Also not sure best way to transparently interrupt the API call to perform those other actions i.e. API call shouldn't trigger its completed or failed actions until after the optional login process completes.
It sounds to me like you'd want an action creator that generates a Thunk, and keep all that logic in the Thunk. There's really no other good way to preserve the association between your suite of API calls, and ensure that all the others are cancelled if one fails.
In that Thunk, you'd fire your API calls, and collect their promises:
const call1 = promiseGenerator1();
const call2 = promiseGenerator2();
const call3 = promiseGenerator3();
const allCallPromises = [call1, call2, call3];
Use an all() promise handler to monitor them:
const watcher = Promise.all(allCallPromises).then(allSuccess, anyFail);
Your fail handler will:
cancel the rest of the promises if any of them 401's. (Note, this requires a library like Bluebird that has cancellation semantics, or some other form of augmentation of your promise/request.)
dispatch an action or route-change to trigger the re-login window
anyFail(error) => {
if (error.status === 401) {
allCallPromises.forEach((item)=> {item.cancel();});
reLogin();
}
}
Then, I'd be inclined to let your relogin component worry about re-firing that same complex action again, to issue all the calls.
However, should your suite of API calls be somehow variable or context-specific, you could cache on the store the ones you need, from inside the anyFail handler. Have a reducer where you can stash an actionPendingReLogin. Compose an action that will re-fire the same calls as last time, and then dispatch it:
dispatch(createAction('CACHE_RELOGIN_ACTION`, actionObjectToSaveForLater));
(Or, just cache whatever action-creator you used.)
Then, following successful relogin, you can:
const action = store.getState('actionPendingReLogin');
dispatch(action);
// or:
const actionCreator = store.getState('actionPendingReLogin');
dispatch(actionCreator());
Oh: and in your allSuccess handler you'd simply dispatch the results of the async calls.

Categories

Resources