How to setState after a loop of async functions - javascript

The closest answer I could find was this, which didn't help since I need to setState: How do I run a function after using array.map?
I think the answer should be simple, but I'm pretty new to Javascript. I'm trying to move the setState for isLoading to AFTER I've pulled all of the profiles.
componentDidMount() {
console.log('MatchesScreen init props: ', Object.keys(this.props))
Object.keys(this.props.profile.profile.matches).map((username) => {
console.log('match', username)
url = 'https://xxxxxxx.execute-api.us-west-2.amazonaws.com/prod/profile?username=' + username
fetch(url, {
method: 'GET',
})
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
})
this.props.addMatch(responseJson)
})
.catch((error) =>{
console.error(error);
})
})
}
I've tried various things like appending a .then() to the map function, but so far no luck.

You could return each promise inside the .map() function, which would leave you with an array of promises. Once you have that, you can use Promise.all() to wait for all the promises in the array to resolve.
componentDidMount() {
console.log('MatchesScreen init props: ', Object.keys(this.props))
// Put all promises into an array
const promisesArray = Object.keys(this.props.profile.profile.matches).map((username) => {
console.log('match', username)
url = 'https://xxxxxxx.execute-api.us-west-2.amazonaws.com/prod/profile?username=' + username;
// Return each promise
return fetch(url, {
method: 'GET',
})
.then((response) => response.json())
.then((responseJson) => {
this.props.addMatch(responseJson)
})
.catch((error) =>{
console.error(error);
});
});
// Wait for all promises in array to resolve
Promise.all(promisesArray)
.then(() => {
// This will execute once all promises have resolved
this.setState({
isLoading: false,
});
})
.catch(e => console.error(e));
}

Try using the async/await pattern as follows:
async componentDidMount() {
...
Object.keys(this.props.profile.profile.matches).map((username) => {
...
await fetch(url, {
...
then move your setState method into its own callback, which you can call in your .then() statements following your fetch.
I like this reference: https://alligator.io/js/async-functions/

Try wrapping the Object.keys() in a while loop with an if statement at the end.
var profilesMatched = 0;
while(profilesMatched < Object.keys(this.props.profile.profile.matches).length){
Object.keys(this.props.profile.profile.matches).map((username) => {
console.log('match', username)
url = 'https://xxxxxxx.execute-api.us-west-2.amazonaws.com/prod/profile?username=' + username
fetch(url, { method: 'GET', })
.then((response) => {response.json()})
.then((responseJson) => {
this.props.addMatch(responseJson);
profilesMatched++;
})
.catch((error) => {
console.error(error);
});
});
if(profilesMatched == Object.keys(this.props.profile.profile.matches).length){
this.setState({
isLoading: false,
})
}
}

Related

Chaining two promises

I have two promises
const promise_1 = this.connection.insertPatientToDataBase(Store.getPotentialPatientID())
.then(ting => {
console.log(ting);
Dispatcher.dispatch({
actionType: Constants.CHANGE_POTENTIAL_PATIENT_PASSWORD,
payload: ting.data.password
})})
.catch(error => {console.log(error)});
const promise_2 = this.connection.getAllPatientData()
.then( function(response) {
console.log("Dispatrinc a new server call")
console.log(response.data)
Dispatcher.dispatch({
actionType: Constants.CHANGE_ALL_PATIENTS,
payload: response.data
})})
.catch(error => console.log(error))
console.log("Done");
}
the first one will post some data to the server, and the second one queries the data to redownload
the new list. The second one is dependent on the first one. Problem is that the first promise is fulfilled after. Second promise is fulfilled first.
How can I chain these two promises together
so promise 2 waits on promise 1?
If both functions are unrelated, but promise_1 has to resolve first so that the patient exists, you can just wrap the promise creation inside a function and only call the promise_2 creation when promise_1 resolves:
const promise_1 = () => this.connection.insertPatientToDataBase(Store.getPotentialPatientID())
.then(ting => {
console.log(ting);
Dispatcher.dispatch({
actionType: Constants.CHANGE_POTENTIAL_PATIENT_PASSWORD,
payload: ting.data.password
})})
.catch(error => {console.log(error)});
const promise_2 = () => this.connection.getAllPatientData()
.then( function(response) {
console.log("Dispatrinc a new server call")
console.log(response.data)
Dispatcher.dispatch({
actionType: Constants.CHANGE_ALL_PATIENTS,
payload: response.data
})})
.catch(error => console.log(error));
promise_1().then( response => promise_2());
If promise_2 relies on the results of promise_1 to run, for example if promise_1 will return the patient id and you need that id to run promise_2 and only the result of promise_2 has to be available after both resolve, then you can modify the above a tiny bit to pass the parameter:
const promise_1 = () => this.connection.insertPatientToDataBase(Store.getPotentialPatientID())
.then(ting => {
console.log(ting);
Dispatcher.dispatch({
actionType: Constants.CHANGE_POTENTIAL_PATIENT_PASSWORD,
payload: ting.data.password
})})
.catch(error => {console.log(error)});
const promise_2 = patient_id => this.connection.getAllPatientData( patient_id )
.then( function(response) {
console.log("Dispatrinc a new server call")
console.log(response.data)
Dispatcher.dispatch({
actionType: Constants.CHANGE_ALL_PATIENTS,
payload: response.data
})})
.catch(error => console.log(error));
promise_1()
.then( patient_id => promise_2( patient_id ))
.then( patient_data => {
// handle patient data.
});
You could also restructure everything into more atomic functions, so each promise has one specific goal, so you can chain them all together. If you nest the structure differently, you can even save all of the responses and return all fo then at the end.
const create_patient_id = () => this.connection.insertPatientToDataBase(Store.getPotentialPatientID());
const create_patient = patient_id => Dispatcher.dispatch({
actionType: Constants.CHANGE_POTENTIAL_PATIENT_PASSWORD,
payload: patient_id.data.password
});
const get_patients = () => this.connection.getAllPatientData();
const update_patients = patients => Dispatcher.dispatch({
actionType: Constants.CHANGE_ALL_PATIENTS,
payload: patients.data
})
const workflow = () => create_patient_id()
.then( create_patient );
.then( get_patients )
.then( update_patients );
workflow();
When using then, you chain promises by creating the next one inside the previous resolver:
const promise_1 = this.connection.insertPatientToDataBase(Store.getPotentialPatientID())
.then(ting => {
console.log(ting);
Dispatcher.dispatch({
actionType: Constants.CHANGE_POTENTIAL_PATIENT_PASSWORD,
payload: ting.data.password
});
return this.connection.getAllPatientData();
})
.then(response => {
console.log("Dispatrinc a new server call");
console.log(response.data);
Dispatcher.dispatch({
actionType: Constants.CHANGE_ALL_PATIENTS,
payload: response.data
});
})
.catch(error => {console.log(error)});
whith async/await this may be easier on the eyes:
async insertAndGet() {
try {
const ting = await this.connection.insertPatientToDataBase(Store.getPotentialPatientID());
console.log(ting);
Dispatcher.dispatch({
actionType: Constants.CHANGE_POTENTIAL_PATIENT_PASSWORD,
payload: ting.data.password
};
const response = await this.connection.getAllPatientData();
console.log("Dispatrinc a new server call");
console.log(response.data);
Dispatcher.dispatch({
actionType: Constants.CHANGE_ALL_PATIENTS,
payload: response.data
})};
} catch (error) {
console.log(error);
}
}
You can simply move second Promise into then section of the first one.
If first promise fails, second one is not executed, if it resolves successfully - second one will commence.
Code will look something like this:
const promise_1 = this.connection.insertPatientToDataBase(Store.getPotentialPatientID())
.then(ting => {
console.log(ting);
Dispatcher.dispatch({
actionType: Constants.CHANGE_POTENTIAL_PATIENT_PASSWORD,
payload: ting.data.password
});
const promise_2 = this.connection.getAllPatientData()
.then(response => {
console.log("Dispatrinc a new server call");
console.log(response.data);
Dispatcher.dispatch({
actionType: Constants.CHANGE_ALL_PATIENTS,
payload: response.data
});
})
.catch(console.log);
})
.catch(console.log);
console.log("Done");
}
You can also chain Promises passing results from one then to another like this:
SomePromiseFunc().then(result1 => SomeOtherPromiseFunc(result1)).then(result2=> doSmth(result2)).catch();
This way could be easier if you want to use result of 1st Promise inside the second one or if catch logic is the same for both of them.
Promise1()
.then(response => Promise2(response))
.catch(err => {
// do something with error
});
This waits until the first promise is resolved, then calls the second promise with the result. You don't have to pass along the result, if you don't need it .then(() => Promise2()). If Promise1 fails then Promise2 is never called.
Note: Apparently I wasn't verbose enough in my initial response, so let's break it down a little better.
First, wrap your promise calls so you can provide the extra functionality to each:
class MyCustomClass {
createNewPatient() { // maybe you pass it in? maybe it's always there?
// Guessing Store is outside the class, but available
return this.connection.insertPatientToDataBase(Store.getPotentialPatientID())
.then(ting => {
console.log(ting);
// Guessing Dispatcher and Constants are outside the class, but available
Dispatcher.dispatch({
actionType: Constants.CHANGE_POTENTIAL_PATIENT_PASSWORD,
payload: ting.data.password
});
})
.catch(error => {console.log(error)});
}
reloadResults() {
return this.connection.getAllPatientData()
.then( function(response) {
console.log("Dispatrinc a new server call")
console.log(response.data)
// Guessing Dispatcher and Constants are outside the class, but available
Dispatcher.dispatch({
actionType: Constants.CHANGE_ALL_PATIENTS,
payload: response.data
});
})
.catch(error => {console.log(error)});
}
// What you seem to be looking for
createAndReload() {
return this.createNewPatient()
.then(() => this.reloadResults())
.then(() => {
console.log('done');
});
}
}

AsyncStorage in a non async function

The following code:
_loginAsync = async () => {
fetch('localhost:3000/login')
.then((response) => response.json())
.then((responseJson) => {
await AsyncStorage.setItem('my-item', responseJson.item);
this.props.navigation.navigate('Home');
})
.catch((error) => {
console.error(error);
});
}
throws the error: Can not use keyword 'await' outside an async function.
Which is the proper way to valorize the my-item?
Your .then callback isn't marked as async, only the outer _loginAsync function is.
_loginAsync = async () => {
fetch('localhost:3000/login')
.then((response) => response.json())
.then(async (responseJson) => {
await AsyncStorage.setItem('my-item', responseJson.item);
this.props.navigation.navigate('Home');
})
.catch((error) => {
console.error(error);
});
}
That said, it seems weird to mix all of the .then and await forms here.
Using Async/Await Only
I think this is the most readable version. We just use async/await to await the fetch directly instead of working with its promise.
_loginAsync = async () => {
try {
const response = await fetch('localhost:3000/login');
await AsyncStorage.setItem('my-item', response.json().item);
this.props.navigation.navigate("Home")
} catch(error) {
console.error(error);
}
}
Using Promises Directly
You can (pretty much) always use an async function as a normal function that returns a promise as well. So instead of awaiting AsyncStorage.setItem we can just use its promise as part of our chain by returning it from then.
_loginAsync = () => {
fetch('localhost:3000/login')
.then((response) => response.json())
.then((responseJson) => AsyncStorage.setItem('my-item', responseJson.item))
.then(() => this.props.navigation.navigate('Home'))
.catch((error) => {
console.error(error);
});
}
If you have to make it work for your code, make the anonymous function for the block where await occurs to async.
_loginAsync = async () => {
fetch('localhost:3000/login')
.then((response) => response.json())
.then(async (responseJson) => {
await AsyncStorage.setItem('my-item', responseJson.item);
this.props.navigation.navigate('Home');
})
.catch((error) => {
console.error(error);
});
}
But I prefer this is a much better approach and looks more readable. Try this code instead.
_loginAsync = async () => {
try {
const response = await fetch('localhost:3000/login');
const responseJson = response.json()
await AsyncStorage.setItem('my-item', responseJson.item);
this.props.navigation.navigate('Home');
} catch (error) {
console.error(error);
}
}

Axios prints value on console but returns undefined

I have quite an issue for some time and is getting on my nerves and it doesn't make sense. I have used axios on my react frontend and it works perfect when assigning the get value to the state. But when using it in a normal javascript code, I appear to have this following issue: i can print the object's value in the console but it will return only undefined.. Here is my code:
login = () => {
let data;
axios.get('https://myaddress/authenticate')
.then(response => {
data = response;
console.log('data here', data);
})
.catch(error => {
console.error('auth.error', error);
});
console.log('eee', data);
return data;
};
Here we are talking about axios strictly.
You can't return an ajax response because it's asynchronous. You should wrap your function into a promise or pass a callback to login
UPDATE: As #Thilo said in the comments, async/await would be another option, but it will let you set the response to data tho ...
1. Wrap into a promise
login = () => new Promise((resolve, reject)=>{
axios.get('https://myaddress/authenticate')
.then(response => {
resolve(response)
})
.catch(error => {
reject(error)
});
});
// Usage example
login()
.then(response =>{
console.log(response)
})
.catch(error => {
console.log(error)
})
2. Pass a callback
login = (callback) => {
axios.get('https://myaddress/authenticate')
.then(response => {
callback(null,response)
})
.catch(error => {
callback(error,null)
});
};
// Usage example
login((err, response)=>{
if( err ){
throw err;
}
console.log(response);
})
3. Async/Await
login = async () => {
// You can use 'await' only in a function marked with 'async'
// You can set the response as value to 'data' by waiting for the promise to get resolved
let data = await axios.get('https://myaddress/authenticate');
// now you can use a "synchronous" data, only in the 'login' function ...
console.log('eee', data);
return data; // don't let this trick you, it's not the data value, it's a promise
};
// Outside usage
console.log( login() ); // this is pending promise
In ES7/ES8 you can do async/await like a boss:
login = () => {
return new Promise((resolve, reject) => {
axios.get('https://myaddress/authenticate')
.then(response => {
resolve(response)
})
.catch(error => {
console.error('auth.error', error);
reject(error)
});
});
};
async function getData() {
try{
const data = await login()
} catch(error){
// handle error
}
return data;
}
getData()
.then((data) => console.log(data));

React Promise.all challenge

I have a situation where I make three fetch calls. Every fetch calls has a callback function which will update the respective property of state.twitterfeed object and finally setState. Issue is that it is calling the setState 3 times as of now. My aim is to use promise.all and update setStatus only once. I tried multiple times but its confusing and challenging.
Code:
this.state = {
twitterfeed: {
techcrunch: [],
laughingsquid: [],
appdirect: []
}
}
updateTwitterFeed = (data, user) => {
const twitterfeed = { ...this.state.twitterfeed
};
if (user === "appdirect") {
twitterfeed.appdirect = data;
} else if (user === "laughingsquid") {
twitterfeed.laughingsquid = data;
} else {
twitterfeed.techcrunch = data;
}
this.setState({
isloadcomplete: true,
twitterfeed
});
};
componentDidMount() {
fetch(
"http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=techcrunch"
)
.then(response => response.json())
.then(data => this.updateTwitterFeed(data, "techcrunch"));
fetch(
"http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=laughingsquid"
)
.then(response => response.json())
.then(data => this.updateTwitterFeed(data, "laughingsquid"));
fetch(
"http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=appdirect"
)
.then(response => response.json())
.then(data => this.updateTwitterFeed(data, "appdirect"));
}
You should have a look at the documentation: Promise.all()
Promise.all() actually preserves the order for its returned values.
Hence you could have:
const promises = [];
promises.push(fetch("http://localhost:7890/1.1/statuses/user_timeline.json?count =30&screen_name=techcrunch"));
promises.push(fetch("http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=laughingsquid"));
promises.push(fetch("http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=appdirect"));
// Execute all promises
Promise.all(promises).then(values => {
console.log(values);
const twitterfeed = { ...this.state.twitterfeed};
twitterfeed.techcrunch = json.parse(values[0]);
twitterfeed.laughingsquid = json.parse(values[1]);
twitterfeed.appdirect = json.parse(values[2]);
this.setState({
isloadcomplete: true,
twitterfeed
});
});
If you are familiar with the axios library.You can use there axios.all([]) calling method. As mentioned in there docs :
function A() {
return axios.get(url,[config]);
}
function B() {
return axios.get(url,[config]);
}
axios.all([A(), B()])
.then(axios.spread(function (result_A, result_B) {
// Both requests are now complete and you can setSate here.
}));
Github : https://github.com/axios/axios
var promise1 = fetch("http://localhost:7890/1.1/statuses/user_timeline.json?count =30&screen_name=techcrunch");
var promise2 = fetch("http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=laughingsquid");
var promise3 =fetch("http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=appdirect");
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
//You can now extend it as you want

Merge api request using promise

Due to the api of a plugin I'm using not working properly. I need to merge the two different requests. I am using the thunk below.
I can get a response but I cannot seem to check for response.ok, and return the combined data:
export function fetchCategories() {
const firstPage =
"http://wordpress.rguc.co.uk/index.php/wp-json/tribe/events/v1/categories?per_page=60&page=1";
const secondPage =
"http://wordpress.rguc.co.uk/index.php/wp-json/tribe/events/v1/categories?per_page=60&page=2";
return dispatch => {
dispatch(isLoading(true));
Promise.all([fetch(firstPage), fetch(secondPage)])
.then(response => {
// check for ok here
response.ForEach(response => {
if (!response.ok) throw Error(response.statusText);
});
dispatch(isLoading(false));
return response;
})
.then(response => response.json())
// dispatch combined data here
.then(data => dispatch(fetchSuccessCategories(data)))
.catch(() => dispatch(hasErrored(true)));
};
}
Any ideas?
You are doing the check for .ok fine because it's in a loop, but your response is actually an array of two Response objects, it does not have a .json() method. You could do Promise.all(responses.map(r => r.json())), but I would recommend to write a helper function that does the complete promise chaining for one request and then call that twice:
function fetchPage(num) {
const url = "http://wordpress.rguc.co.uk/index.php/wp-json/tribe/events/v1/categories?per_page=60&page="+num;
return fetch(url).then(response => {
if (!response.ok)
throw new Error(response.statusText);
return response.json();
});
}
export function fetchCategories() {
return dispatch => {
dispatch(isLoading(true));
Promise.all([fetchPage(1), fetchPage(2)]).then(data => {
dispatch(isLoading(false));
dispatch(fetchSuccessCategories(merge(data)));
}, err => {
dispatch(isLoading(false));
dispatch(hasErrored(true));
});
};
}

Categories