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

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

Related

Export const async function => assign the result to a const

I am trying to store the result of an async function to a const but with no success.
The code:
Here is working ok and return the converted json
import pako from 'pako';
export const unzipFile = async (json_to_unzip) => {
try {
// fetch file with CORS enabled
const res = await fetch(json_to_unzip, {
mode: 'cors',
});
// convert to arrayBuffer for further processing
const buf = await res.arrayBuffer();
// and convert blob to arrayBuffer using `await blob.arrayBuffer()`
console.log('input size: ', buf.byteLength);
// decompress file
const outBuf = pako.inflate(buf);
console.log('output size: ', outBuf.byteLength);
// convert arrayBuffer to string
const str = new TextDecoder().decode(outBuf);
// print json object
const unzippedFile = JSON.parse(str);
console.log('json object', unzippedFile[0]);
return unzippedFile[0];
} catch (err) {
console.error('unable to decompress', err);
}
};
From here I want to store it to a const:
const emissions = (async () => {
const res = await unzipFile(emissions_json);
return res;
})();
const emissions = unzipFile(emissions_json).then(res => res)
console.log(emissions) still returns a pending promise in both cases
Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.
Docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
emissions is a promise because it holds the promise object returned from the async anonymous function. To console.log the result you can do:
emissions.then(res => console.log(res))
This happens because you are trying to manage the result of an asynchronous operation so you can not treat it as if it was a synchronous operation. With that said, to store the result to a const you need to do it inside a proper async function as you did with the await keywoard or by using a Promise.then() handler:
unzipFile(emissions_json).then(res => {
//logic for using the res
})

An array of promises is returned when using axios in map function?

I am mapping through this dummyAssests array and want to create a new array of objects based on the axios response called newAssetArray. But when I console.log the newAssetArray it just contains an array of Promises...
useEffect(() => {
let newAssetArray = dummyAssets.map(async function (asset) {
return await axios
.get(currrentPrice(asset.asset_id))
.then((response) => {
let cp = response.data.market_data.current_price.eur;
let value = Number(cp) * Number(asset.amount);
return { ...asset, value: +value };
})
.catch((error) => console.log(error.response.data.error));
});
setAssets(newAssetArray);
console.log(newAssetArray);
}, [dummyAssets]);
Console:
Yes, that is expected. Use Promise.all() to wait for all the promises to resolve. .map() itself is not promise aware so it does not "wait" for each individual iteration of its loop to resolve before going onto the next one. So, you have the result of calling N async functions which all return a promise as your .map() result. That is how the code is supposed to work. The await you are using it not accomplishing anything. If you use a for loop instead (which is promise-aware, then your loop will be sequenced.
Here's how you could use Promise.all() to get the results:
useEffect(() => {
Promise.all(dummyAssets.map(function(asset) {
return axios
.get(currrentPrice(asset.asset_id))
.then((response) => {
let cp = response.data.market_data.current_price.eur;
let value = Number(cp) * Number(asset.amount);
return { ...asset, value: +value };
}).catch((error) => {
console.log(error.response.data.error)
throw error;
});
})).then(newAssetArray => {
console.log(newAssetArray);
setAssets(newAssetArray);
}).catch(err => {
// do something to handle the error here
});
}, [dummyAssets]);
Or, you might find this implementation a bit simpler to follow:
useEffect(async () => {
try {
const newAssetArray = await Promise.all(dummyAssets.map(async function(asset) {
const response = await axios.get(currrentPrice(asset.asset_id));
const cp = response.data.market_data.current_price.eur;
const value = Number(cp) * Number(asset.amount);
return { ...asset, value: +value };
}));
console.log(newAssetArray);
setAssets(newAssetArray);
} catch (e) {
// do something to handle the error here
console.log(e);
}
}, [dummyAssets]);
Also, note that a structure such as:
async function someFunction() {
return await axios.get()
}
does nothing different than just:
async someFunction() {
return axios.get()
}
They both return a promise that resolves to whatever axios.get() resolves to. The first returns a promise because it's in an async function and ALL async functions return a promise at the point they hit the first await. The second returns a promise because you're directly returning the axios.get() promise. Either way, they both return a promise that resolves to the same thing.
So, in general return await fn() is not helping you vs just return fn() and is not recommended. Then, once you stop using await there, you don't need async for that .map() callback anymore either.

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

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))
}
})
})
})
}

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

getting child categories base on parent category id using promises

Understanding: my little understanding with promise is we can use it in place of callback functions.
Scenario: i will fetch parent categories from api than pass the categories array of objects to a function, a promise function may be ?
const getData = async () => {
const res = await fetch('API/categories?parent=0');
const data = await res.json();
return Promise.all(data.map(item => anAsyncFunction(item)))
}
this function recieves all the categories and i pass it to promise function
const anAsyncFunction = async item => {
return functionWithPromise(item)
}
this returns right data all child categories in array of objects
const functionWithPromise = async data => { //a function that returns a promise
const res = await fetch('API/categories?parent='+data.id);
const datas = await res.json();
// console.log(datas);
// api call to insert all the records
return Promise.resolve(data)
}
now i want to go through all these arrays and insert into database using api call
await api.create(postData)
You can merge anAsyncFunction & functionWithPromise functions. Two functions are unnecessary.
The line return Promise.resolve(data) can be return data
getData is thenable which gives you array of promise responses. Loop thru it twice and use Promise.all
const getData = async () => {
const res = await fetch("API/categories?parent=0");
const data = await res.json();
return Promise.all(data.map((item) => anAsyncFunction(item)));
};
const anAsyncFunction = async (data) => {
const res = await fetch("API/categories?parent=" + data.id);
const datas = await res.json();
// api call to insert all the records
// return Promise.resolve(data);
return datas;
};
getData().then((res) => {
let promises = [];
res.forEach((datas) => { //this loop is for each and every response
datas.forEach((postData) => { //this loop is for
promises.push(api.create(postData));
});
});
Promise.all(promises).then((finalRes) => {
console.log("finalRes", finalRes);
});
});

Resources