I'm using react and I have an asynchronous action that receives some data from API using axios. I also have a flag (state variable tableLoaded) which describes if data is fetched.
this.props.fetchDataAction(requestParams).then(
() => {
this.setState({
data: this.props.reports.data
});
}
).then(() => {
this.setState({ tableLoaded: true })
});
I want my flag tableLoaded to be set to true in both cases - either after API call succeded and failed, so I just added another then() on my Promise, which triggers function that sets this flag to true.
My question is - is this the best solution to achieve my goal? Or should I repeat this code in both cases?
You should use the Promise.finally syntax.
this.props.fetchDataAction(requestParams)
.then(() => {
// Do your thing on success
this.setState({
data: this.props.reports.data
});
})
.catch((error) => {
// Do something if failed
})
.finally(() => {
// Do this in all cases..
this.setState({ tableLoaded: true })
});
Edit:
If the return from fetchDataAction is an Axios promise, then you should replace .finally by .then because Axios doesn't offer the finally method. I would then say that your original suggestion was correct. You could comment the second .then so you know why.
this.props.fetchDataAction(requestParams)
.then(() => {
// Do your thing on success
this.setState({
data: this.props.reports.data
});
})
.catch((error) => {
// Do something if failed
})
.then(() => { // Triggered in all cases by axios
// Do this in all cases..
this.setState({ tableLoaded: true })
});
You can use all() to catch success and failures
One issue you'll run into with the current approach is that any possible errors will prevent the last .then from running, making it possible for tableLoaded to remain false if something does go wrong. See this pen for an example of this issue.
Promise.finally is one good way to step around this, as another answer points out, but my personal preference would be to use async/await.
try {
await this.props.fetchDataAction(requestParams)
this.setState({
data: this.props.reports.data
})
} catch (error) {
// handle error
}
this.setState({ tableLoaded: true })
Related
I'm using Axios for API services and just curious if there's any official way to handle a "complete" event as we have used in Ajax call.
So like
axios.get('/v1/api_endpoint?parameters')
.then((res) => { .. })
.catch((err) => { .. })
.complete(() => {}) // <== is there any way to handle this complete event?
Following the axios documentation here, the secondary .then() is the one that I'm looking for.
Here's a good example of how to handle that axios complete event which will always be executed whether it has succeeded or failed.
axios.get('/v1/api_endpoint?with_parameters')
.then((res) => { // handle success })
.catch((err) => { // handle error })
.then(() => { // always executed }) <-- this is the one
If you have to check whether an API call is a success or not, then you can use below code:
const response = await axios.post(
"http://localhost:8000/xyz",
{ token, user }
);
const status = response.status
if (status == 200) {
console.log('Success')
toast("Successful Transaction", { type: "success" });
} else {
console.log('Falure')
toast("Falure", { type: "error" });
}
You can also use finally to check if the event is completed or not. Attaching the link for your reference:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
I have the following chain.
return axios
.get(actionUrl, {
params: {
action: 'action3'
},
})
.finally(() => axios.get(actionUrl, {
params: {
action: 'action3'
},
}))
.finally(() => axios.get(actionUrl, {
params: {
action: 'action6'
},
}))
.finally(() => axios.get(actionUrl, {
params: {
action: 'action1'
},
}))
I have to sequentially call different endpoints in order even if the previous one fails. However in case an endpoint timeouts I want to break the chain. Is it achievable without using .then and .catch and repeating the same code in them?
Thanks.
The finally function is there precisely to make sure that the function inside runs even if there is an exception. You can get the behaviour you want by using just one finally like this:
axios.get()
.then(() => doStuffOnSuccess())
.finally(() => {
axios.get().then(() => doFinallyStuff1())
.then(() => doFinallyStuff2())
.then(() => doFinallyStuff3())
.catch(e => console.error("Finally had trouble",e));
});
This way if anything within the finally function times out or fails it will break the chain. By having the final catch you will avoid it throwing back further up the chain.
This assumes that you are using finally correctly and everything in that should always get executed after the previous calls finish even if there are errors.
This is achievable with then and catch. You should not use finally if you don't want the callback to run in case of an error.
I have to sequentially call different endpoints in order even if the previous one fails. However in case an endpoint timeouts I want to break the chain
So you want to not call them when the previous one fails (with a timeout), all you want to do is to ignore non-timeout errors. That's what catch should be used for:
function callEndpoint(action) {
return axios.get(actionUrl, { params: { action } }).catch(err => {
if (isTimeout(err))
throw err
else
; // ignore the error, return undefined
})
}
Then just chain them:
callEndpoint('action3').then(() => callEndpoint('action6')).then(() => callEndpoint('action3'))
Are you familiar with async/await? Generally you shouldn't chain finally like this, it's always better to create recurent function for example:
const fetchSomething = async () => {
try {
const result = await axios.get();
if (...when fetching should stop...) {
return result;
}
return fetchSomething();
} catch(error) {
return fetchSomething();
}
}
But with reccurent function is extremely important to create some kill switch to prevent executing it forever - for example set some kind of timeout, 1 minute or so and if this limit is exceeded then stop executing.
It will be probably even more easier with generators and yield but I never used this solution
I have given up on what I feel is simple. I have the following promise:
information.js
// other methods
export async function information() {
let info= {
code: ''
};
await AsyncStorage.multiGet([
'code'
])
.then((response) => {
info['code'] = response[0][1]
})
.catch((e) => {
console.log(e);
});
return info;
}
process.js:
import { information } from "../information"
Promise.all([information()]).then(function (values) {
if (values[0]['code'] != null) {
// tag the code
}
}).catch(err=>{
console.log(err)
});
now in processor.test.js
import * as info from '../information';
it("should tag service code", async () =>{
const spy = jest.spyOn(info,"information")
spy.mockResolvedValue({'code':'ABC'})
expect(tagCode()).toEqual('ABC_Y')
});
it fails saying expected 'ABC_Y' but got null. From console.log on the resolved Promise, I can see it is executing the original information method, instead of my spy thus returning null always.
Please correct me if I'm on the wrong track, however could this be solved by changing your test case slightly?
jest.spyOn(info, 'information').mockImplementationOnce(jest.fn(async () => { code: 'ABC' });
expect(tagCode()).toEqual('ABC_Y');
I haven't tested this code, just my opinion at 4:42am.
I opt to include a jest.fn call within my mockImplementation call, this allows me to test for other things such as information() being called:
expect(info.information).toHaveBeenCalledTimes(1);
expect(info.information).toHaveBeenCalledWith({ code: 'ABC' })
Hope this goes at-least some way toward helping you resolve your issue, although I'll admit I've had many an issue with Jest (especially with dependencies, although these are usually my mistake through context issues).
I've read your question a few times and I'm still not convinced I truly understand it, so please accept my apologies if above is useless to you.
export async function information() {
let info= {
code: ''
};
await AsyncStorage.multiGet([
'code'
])
.then((response) => {
// this may produce different results
info['code'] = response[0][1]
})
.catch((e) => {
console.log(e);
});
return info;
}
An issue could be above, you're returning info, I'm assuming with the understanding it could contain the resolved value, from your .then statement, as far as I'm aware this wouldn't work in reality.
The .then is processed at the end of the method (after return), so your info could contain an empty code, then some time after would complete the Promise.
I'd change that from above, to:
export async function information() {
let info= {
code: ''
};
await AsyncStorage.multiGet([
'code'
])
.then((response) => {
info['code'] = response[0][1]
Promise.resolve(info);
})
.catch((e) => {
console.log(e);
});
}
Although I'd recommend not mixing async/await with Promises, as it's a pretty good way to shoot yourself in the foot (my opinion of course).
You can test this theory of course by inserting a comment above your return and inside your .then, a simple console.log('called') | console.log('called1') will give you an indication of which was called first.
I am dealing with the following frustrating error:
Home.js:231 Uncaught (in promise) TypeError: _this9.setState is not a function. The error is coming from the last line of the following function:
checkIfRunning() {
return fetch('/api/following/iscurrentlyrunning', {
credentials: 'include',
})
.then(response => {
console.log(response.status);
if (response.status === 200) {
return response.json();
}
})
.then(response => {
let cState = this.state;
cState.running = response;
this.setState(cState);
});
}
I did bind the function in the component constructor and when I call it alone, it works fine. The issue arise when I try to invoke the function in a timer (setInterval). In componentWillMount, I call few functions:
componentWillMount() {
this.checkIfFirstTimeLogin()
.then(() => {
// user already exists
if (!this.state.firstLogin) {
this.Name();
this.getRole();
setInterval(() => this.checkIfRunning(), 10000);
}
})
.then(() => {
let cState = this.state;
cState.pageLoading = false;
this.setState(cState);
})
.catch(error => console.log(error));
}
I have the intuition that the promise chain breaks the binding for a reason I do not presently understand.
Thank you for any help,
Promises are a guaranteed future, which means the whole promise chain will fire once invoked and there's little you can do to stop it.
On a practical level, this means you need to check to be sure that your component instance is still mounted before trying to access setState off it, as the component may have unmounted before this promise chain completes.
.then(response => {
...code here...
// important! check that the instance is still mounted!
if (this.setState) {
this.setState(cState);
}
});
Also, you should never mutate local state directly as you are doing here:
// don't mutate state directly, use setState!
let cState = this.state;
cState.running = response;
You are mutating the state directly, it is not allowed, in the final example you are still doing it. It is better to use an Object.assign(…) to create new object like this :
let newState = Object.assign({}, ...this.state, running: response);
Then, only do your setState() call
this.setState(newState);
One of the fundamental principles of React is that changes to State are not done directly but with the setState function which will put the change to queue, and it will be done either alone or with batch update.
You can try change function checkIfRunning() {} to checkIfRunning = () => {} to pass this into function
Thanks all for the help, very appreciated.
I solved the problem with the following fix, although I am not sure why it works now:
checkIfRunning() {
return fetch('/api/following/iscurrentlyrunning', {
credentials: 'include',
})
.then(response => {
console.log(response.status);
if (response.status === 200) {
return response.json();
}
})
.then(response => {
let cState = this.state;
cState.running = response;
this.setState({cState});
});
}
Notice how this.setState(cState) became this.setState({cState}).
Thanks all for your time, it led to interesting research on my part.
I've looked through a few posts such as this post
I want to use a console.log to see if I successfully set an item to AsyncStorage.
Here is my code:
export function saveDeckTitleAPI(key,title) {
return AsyncStorage.setItem(uuid(), JSON.stringify(new DeckCreator(title)))
.then(data => {
debugger;
console.log('INSIDE SET ITEM');
AsyncStorage.getItem(data.key).then(item => {
console.log(item);
})
})
.catch(err => {
console.err(err);
});
}
When I run this code, the .then and the .catch aren't fulfilled. I tried logging the promise by itself, and I get a similar result as the post above.
Do I have to use async/await? Is that the problem here? Here are the docs to setItem.
You can pass a callback as the third argument. If there's an error, it will be the callback's first parameter. If there's no error, console log that all is well and good, otherwise log the error.
Yes you need async and await
You can get an inspiration from the code below, the way I do a facebook login with setItem
const doFacebookLogin = async dispatch => {
const { type, token } = await
Facebook.logInWithReadPermissionsAsync('xxxx', {
permissions: ['public_profile']
});
if (type === 'cancel') {
return dispatch({ type: FACEBOOK_LOGIN_FAIL });
}
await AsyncStorage.setItem('fb_token', token);
dispatch({ type: FACEBOOK_LOGIN_SUCCESS, payload: token });
};