How tu turn this function into a thunk react promise function (Updated) - reactjs

I am fetching data from the blockchain. Contract adresses in this case.
Once I have the adresses I fetch some info on each specific adresses and add a key=>value pair to the object. This is all working and I'm getting all the right data. However, once in the component, the newly added key=>value pair is no longer present. I think this is because the value added is a promised and the dispatch is not waiting on it. How can I fix this so the dispatch it done only once the promised is resolved.
const tokenStream = await exchange.getPastEvents('OtherToken', {fromBlock:0, toBlock: 'latest'})
const allTokens = tokenStream.map((event) => event.returnValues)
console.log('ALL TOKEN DATA : ', allTokens)
allTokens.forEach( async element => {
let symbol = await exchange.methods.getERCsymbol(element.tokenAddress).call()
element.symbol = symbol
});
console.log('ALL TOKEN DATA AFTER : ',allTokens) // I see symbol
dispatch(allTokensLoaded(allTokens))

Better solution would be to use Promise.all to wait for multiple async request/promise to finish, and also you are mixing await and then, as your main function is already async you can write it in more neat and clean way using await only.
export const loadAllTokens = async (exchange, dispatch) => {
const result = await exchange.getPastEvents('OtherToken', {fromBlock:0, toBlock: 'latest'});
const allTokens = result.map((event) => event.returnValues);
await Promise.all(allTokens.map(async (element) => {
const innerResult = await exchange.methods.getERCsymbol(element.tokenAddress).call();
element.symbol = innerResult;
element[2]= innerResult;
}));
dispatch(allTokensLoaded(allTokens));
}
its more clean and better to understand :).
if any doubts please comment.

They happens to be nested async request and this is how I fixed it
export const loadAllTokens = async (exchange, dispatch) => {
await exchange.getPastEvents('OtherToken', {fromBlock:0, toBlock: 'latest'}).then( async(result) => {
const allTokens = result.map((event) => event.returnValues)
let count = 0
allTokens.forEach( async element => {
await exchange.methods.getERCsymbol(element.tokenAddress).call().then( async(result) =>{
element.symbol = result
element[2]= result
count += 1
}).then( () => {
if(count === allTokens.length){
dispatch(allTokensLoaded(allTokens))
}
})
})
})
}

Related

What is the proper way to make api calls and get the author information of each post by id?

I'm building a blog app and I want to list all the posts stored in MongoDB. Each post object has an author id, and I want to get the posts and then get the author name of each post and add it as an attribute to the post object.
I defined the methods getUserById and getPosts to make api calls to a Node.js backend, and they both work fine.
Now I'm doing something like this in the useEffect hook of the component, which does not work properly:
useEffect(() => {
const fetchPosts = async () => {
const res = await getPosts();
let posts = res.data;
posts.map(async (post) => {
const res = await getUserById(post.userId);
post.username = res.data.username;
});
setPosts(posts);
};
fetchPosts();
}, []);
I'm wondering what is the proper way to implement something like this. Thanks.
Let add async to effects function and await for fetchPosts():
useEffect(async () => {
const fetchPosts = async () => {
const res = await getPosts();
let posts = res.data;
posts.map(async (post) => {
const res = await getUserById(post.userId);
post.username = res.data.username;
});
setPosts(posts);
};
await fetchPosts();
}, []);

Multiple nested axios calls don't resolve as expected

As described in comments between my code snippet, the asynchronicity is not working as expected. For each id, an object/item should return but it only returns one item since my async await isn't implemented properly. What could be a possible workaround?
Thanks in advance
useEffect(() => {
axios.get('url-here').then((res) => {
res.data.favProperties?.map((el) => {
console.log(el) // this returns multitple id's of saved/liked items
axios.get('url-here').then(async (r) => {
if (r.data) {
console.log(r.data) // Problem starts here
// This returns the full object of the liked items
// But only one object is returned, not every object for which an id was stored
await storageRef
.child(r.data.firebaseRef + '/' + r.data.images[0])
.getDownloadURL()
.then((url) => {
// Here i need to fetch the image for each object
console.log(url)
})
.catch((err) => console.log(err))
}
})
})
})
}, [])
I think breaking down your operations into functions will prevent this Promise Hell. I would recommend using async await for these kinda operations. Also I was confused about the last part of console logging the download URL, by my guess you're trying to save all the download URLs for these liked items in an array.
useEffect(() => {
firstFunction();
}, []);
const firstFunction = async () => {
const { data } = await axios.get("url-here");
const favProperties = data.favProperties;
const fetchedUrls = await Promise.all(
favProperties?.map(async (el) => (
await secondFunction(el.id) /** use el to pass some ID */
))
);
};
const secondFunction = async (someId) => {
/** your second URL must point to some ID (or some parameters) specific API otherwise
running same op in a loop without variations doesn't make any sense */
const { data } = await axios.get(`some-other-url/${someId}`);
if (data) {
console.log(data);
const fetchedUrl = await storageThing(data);
return fetchedUrl;
}
};
const storageThing = async ({ firebaseRef, images }) => {
try {
const downloadURL = await storageRef
.child(firebaseRef + "/" + images[0])
.getDownloadURL();
console.log(downloadURL);
return downloadURL;
} catch (error) {
console.log(error);
return '';
}
};

How does this code work in this specifics regarding async wait

I learn some React and Redux and have a beginner question.
In the GitHub code below there are two method calls getInitUsers() and getMoreUsers().
Here is Original GitHub code for the below code
....
useEffect(() => {
const getUsers = async () => {
await getInitUsers();
}
getUsers();
}, [getInitUsers]);
const addMoreUsers = () => {
if(!isGettingMoreUsers) {
getMoreUsers(prevDoc);
}
}
....
const mapDispatchToProps = dispatch => ({
getInitUsers: () => dispatch(asyncGetInitUsers()),
getMoreUsers: prevDoc => dispatch(asyncGetMoreUsers(prevDoc))
})
...
The Redux action for the above getInitUsers() and getMoreUsers() are this two:
Here is the original GitHub code for the below code
export const asyncGetInitUsers = () => {
return async dispatch => {
try {
dispatch(getUsersStart());
const usersRef = firestore.collection("users").orderBy("registrationDate", "desc").limit(30);
usersRef.onSnapshot(docSnapShot => {
let users = [];
docSnapShot.docs.forEach(doc => {
users.push({id: doc.id, data: doc.data()});
});
dispatch(getUsersSuccess(users));
const lastDoc = docSnapShot.docs[docSnapShot.docs.length - 1];
dispatch(setPrevDoc(lastDoc));
});
} catch (errorMsg) {
dispatch(getUsersFailure(errorMsg));
}
}
}
export const asyncGetMoreUsers = prevDoc => {
return async dispatch => {
try {
dispatch(getMoreUsersStart());
const usersRef = firestore.collection("users").orderBy("registrationDate", "desc").startAfter(prevDoc).limit(30);
usersRef.onSnapshot(docSnapShot => {
let users = []
docSnapShot.docs.forEach(doc =>{
users.push({id: doc.id, data: doc.data()})
});
dispatch(getMoreUsersSuccess(users));
const lastDoc = docSnapShot.docs[docSnapShot.docs.length - 1];
dispatch(setPrevDoc(lastDoc));
});
} catch (e) {
dispatch(getMoreUsersFailure(e));
}
}
}
I understand placing the getInitUsers() in the useEffect() will make it run once on Component creation. What I want to ask is what does this await do on this line:
await getInitUsers();
If you look at the getMoreUsers() it does not have the await and if you look at the two action asyncGetInitUsers() and asyncGetMoreUsers() abowe they have the same logic and starts with:
return async dispatch => {...
So what is the difference here? getInitUsers() and getMoreUsers()??
I created a CodeSandbox to try to understand the await
In this case, the await does nothing different, there's actually no point in using async await because you aren't doing anything after the await or returning a value from it.
So, the getInitUsers bit could be simplified to:
useEffect(() => {
getInitUsers();
}, [getInitUsers]);
For example, if you wanted to run some code after the getInitUsers finished. For example, a loading boolean:
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const getUsers = async () => {
await getInitUsers();
setLoading(false);
};
getUsers();
}, [getInitUsers]);
Though due to the simplicity of the code, this could be simplified by using promises directly:
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
getInitUsers().then(() => {
setLoading(false);
});
}, [getInitUsers]);
For some documentation on async await and what it does for us: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await, but here's a bit of an intro:
Essentially, async/await is a combination of features that allow for easier asynchronous flows in JS.
These features basically act as syntactic sugar on top of promises,
making asynchronous code easier to write and to read afterwards. They
make async code look more like old-school synchronous code, so they're
well worth learning. This article gives you what you need to know.
async functions always return a promise, and they do so the moment they are called.
When the async function finishes execution, then that promise resolves to the value returned from it (undefined if nothing is returned) or is rejected with an error.
await only works in async functions (though there is a proposal that's in stage 3 for Top Level Await). await takes a promise and waits for it to be resolved or rejected before continuing execution and unwrapping the promise.
So, without async/await, you need to use the .then or .catch functions of promises, but with async/await, you can do a lot to reduce callback hell.
Here's some very contrived examples to show how using the async/await syntatic sugar can make code easier to read and reason through. Though the biggest danger is also that async/await makes the code look synchronous even though it's not.
// Setting up a couple promises
let promise = new Promise(resolve => resolve(42));
let promise2 = new Promise(resolve => resolve(8));
// Using promises to multiply them together
Promise.all([promise, promise2])
.then(([value, value2]) => value * value2)
.then(value => console.log('promises', value))
// Setting up a couple promises
let promise3 = new Promise(resolve => resolve(42));
let promise4 = new Promise(resolve => resolve(8));
// Using async/await to multiply them together
(async() => {
let value = await promise3 * await promise4;
console.log('async/await', value);
})()

Trouble fetching data with Async/Await

Hi I'm trying to fetch a country's data after which I want to fetch the names for its neighboring countries.
import React from 'react';
export default function DetailsRoute({ match }) {
const [countryData, setCountryData] = React.useState({});
const [borderCountries, setBorderCountries] = React.useState([]);
React.useEffect(() => {
fetchCountryData();
}, [])
const fetchCountryData = async () => {
/* fetch country by Name */
const response = await fetch(`https://restcountries.eu/rest/v2/name/${match.params.country}`);
const fetchedData = (await response.json())[0];
setCountryData(fetchedData);
const neighbors = [];
/* Extract 'alphaCode' for each bordered country and fetch its real name */
fetchedData.borders.forEach(async (alphaCode) => {
const response = await fetch(`https://restcountries.eu/rest/v2/alpha/${alphaCode}`);
const fetchedNeighbor = await response.json();
neighbors.push(fetchedNeighbor.name);
});
/* THIS DOESN'T WAIT FOR NEIGHBORS TO BE FILLED UP */
setBorderCountries(neighbors);
}
return (
<article>
<h1>{countryData.name}</h1>
{borderCountries.map(countryName => <h2>{countryName}</h2>)}
</article>
)
}
As you can see, the setBorderCountries(neighbors) doesn't run asynchronously. But I have no idea how to make it wait for the forEach() loop to finish.
Somewhere on stackoverflow, I saw Promise.all() and tried to implement it but I really don't know if it's syntactically correct or not-
Promise.all(
fetchedData.borders.forEach(async (alphaCode) => {
const response = await fetch(`https://restcountries.eu/rest/v2/alpha/${alphaCode}`);
const fetchedNeighbor = await response.json();
neighbors.push(fetchedNeighbor.name);
})
)
.then(() =>
setBorderCountries(neighbors)
)
My question is how do I make the setBorderCountries(neighbors) wait until forEach() loop finishes filling up neighbors?
And maybe some suggested optimization to my code?
The forEach loop finishes immediately, since the waiting happens in the callbacks (only), but those callbacks are all called synchronously, and so the forEach finishes before any of the awaited promises resolve.
Instead use a plain for loop:
for (let alphaCode of fetchedData.borders) {
Now the await inside that loop is part of the top level async function, and so it works as you intended.
You can also consider using Promise.all if it is acceptable that promises are created without waiting for the previous one to resolve. And then you can await the result of Promise.all. In your attempt at this, you did not pass anything to Promise.all, as forEach always returns undefined. The correct way would use .map as follows:
const neighbors = await Promise.all(
fetchedData.borders.map(async (alphaCode) => {
const response = await fetch(`https://restcountries.eu/rest/v2/alpha/${alphaCode}`);
const fetchedNeighbor = await response.json();
return fetchedNeighbor.name; // return it!!
});
)
setBorderCountries(neighbors);
Note that here also the .map iteration finishes synchronously, but it returns an array of promises, which is exactly what Promise.all needs. The awaiting happens with the await that precedes Promise.all.
Promise.all takes an array of promises. Your code is passing the result of the forEach call (undefined) to Promise.all, which isn't going to do what you want.
If you use map instead you can create an array of request promises. Something along the lines of:
const fetchNeighbor = async (alphaCode) => {
return fetch(`https://restcountries.eu/rest/v2/alpha/${alphaCode}`)
.then(response => response.json())
.then(n => n.name);
}
const neighborNames = await Promise.all(fetchedData.borders.map(fetchNeighbor));
setBorderCountries(neighbors);
Array.map produces a new array by running the given function on every element in the source array. So these two are roughly equivalent:
const promises = fetchedData.borders.map(fetchNeighbor);
const promises = [];
fetchedData.borders.forEach(alphaCode => {
promises.push(fetchNeighbor(alphaCode));
});
You now have an array of promises (because that's what fetchNeighbor returns) you can pass to Promise.all:
const results = await Promise.all(promises);
Promise.all resolves with an array of the resolved promise values. Since fetchNeighbor ultimately resolves with the name, you now have an array of names.
const results = await Promise.all(promises);
console.log(results);
// ['Country A', 'Country B', 'Country C', ... ]
I think you should use something like this.
const start = async () => {
await asyncForEach([1, 2, 3], async (num) => {
await waitFor(50);
console.log(num);
});
console.log('Done');
}
start();
this article I think is a good resource to learn: async/await with for each

Get data from a promise. Or is it promise at all?

I store some data in IndexedDB and i use npm package localforage for it.
const retrieveData = async () => {
const keys = await localforage.keys()
const data = await keys.map(async (key) => {
const item = await localforage.getItem(key)
return [item.username, item.compamy, item.email, item.lastUpdate]
})
return data
}
Whenever I execute this function, I get a resolved Promise object, which values I cannot extract
async componentDidMount() {
let asyncData = retrieveData()
console.log(asyncData) // Promise object
asyncData = retrieveData().then(values => values)
console.log(asyncData) // Promise object anyways
}
How exactly should I get data from this Promise object?
const retrieveData = async () => {
const keys = await localforage.keys()
// The return value of "keys.map" is an array of promises since
// async automatically returns a Promise behind the scenes.
// Await works on a single promise, not an array of promises,
// so "data" will not contain the actual data.
const data = await keys.map(async (key) => {
const item = await localforage.getItem(key)
return [item.username, item.compamy, item.email, item.lastUpdate]
})
return data
}
Do:
const retrieveData = async () => {
const keys = await localforage.keys()
const data = await Promise.all(keys.map(async (key) => {
const item = await localforage.getItem(key)
return [item.username, item.compamy, item.email, item.lastUpdate]
}));
return data
}
Or use Bluebird's map which works out of the box in this scenario:
// The "then" function does not do anything. It returns values,
// but only does so to the next "then" function. There are no
// further then-functions so the return value is unused.
// "values" is merely a local variable so you won't be able to
// access it anywhere outside the fat arrow function.
// You could move the console log into "then".
asyncData = retrieveData().then(values => values)
// asyncdata is still the unresolved promise object, the "then"
// function has not been run yet (then on the line above will be run when
// all of the awaits in retrieveData have been successfully resolved.
console.log(asyncData)
Do:
async componentDidMount() {
const data = await retrieveData();
console.log(data);
}
Or:
componentDidMount() {
retrieveData().then(values => {
console.log(values);
});
}
you can use for of loop here, and it will be simpler, we dont use await keyword directly in a map iterator, instead we can use promise.all as mentioned in above answer.
const retrieveData = async () => {
const keys = await localforage.keys();
let data;
for (let key of keys) {
const item = await localforage.getItem(key);
data.push([item.username, item.compamy, item.email, item.lastUpdate]);
}
return data;
}
Try using the "await" reserved keyword before your retrieveData() method at componentDidMount(), since it's a promise, an async event, you have to wait until it finishes all of it's inner executions to return some data and go on.
Just like you did at retrieveData() declaration use await before the promise. in detail what you need:
async componentDidMount() {
let asyncData = await retrieveData()
....
}

Resources