Dynamically add values from different json responses - JS - javascript

I am calling an API and getting the total_tweet_count. If the meta tag contains a next_token, I fetch the next API (here I call the corresponding object) with the next_token and push the total_tweet_count to an array. However, if the response doesnt contain a next_token, I stop iterating and just push the total_tweet_count and return the array. For some reason, the code doesnt seem to run. Please help.
The below example should get the next_token from res1 and call res2 and push the tweet_count into the array. Then using the next_token of res2, fetch res3 data and push the count. Since res3 doesnt have a next_token, we end the loop and return the data. This is the expectation. Please advice.
const getData = async () => {
const totalCount = [];
const results = await Promise.resolve(data.res1);
totalCount.push(results.meta.total_tweet_count)
do {
const results1 = await Promise.resolve(data[`res${results.meta.next_token}`]);
totalCount.push(results1.meta.total_tweet_count)
}
while (results.meta.next_token)
return totalCount;
}
getData().then(res => console.log(res))
The final output I expect is [1,20, 8]
Please advice.
Fiddle Link: https://jsfiddle.net/czmwtj3e/

Your loop condition is constant:
const results = await Promise.resolve(data.res1);
//^^^^^^^^^^^^^
…
do {
…
}
while (results.meta.next_token)
What you are looking for is
const getData = async () => {
const totalCount = [];
let results = await Promise.resolve(data.res1);
totalCount.push(results.meta.total_tweet_count)
while (results.meta.next_token) {
results = await Promise.resolve(data[`res${results.meta.next_token}`]);
totalCount.push(results.meta.total_tweet_count)
}
return totalCount;
}
or
const getData = async () => {
const totalCount = [];
let results = {meta: {next_token: 1}};
do {
results = await Promise.resolve(data[`res${results.meta.next_token}`]);
totalCount.push(results.meta.total_tweet_count)
}
while (results.meta.next_token)
return totalCount;
}
Notice there is no const results1 in either of these, but an assignment to the mutable results variable.

As far as I can tell:
getData() is a wholly synchronous process acting on data therefore does not need to be an asyncFunction.
the required array of tweet counts can be created with a combination of Object.keys() and Array.prototype.reduce().
the .next_token property does not need to be used (unless it has some secret meaning beyond what has been explained in the question).
const getData = () => {
const keys = Object.keys(data); // array ['res1', 'res2', 'res3']
const totalCounts = keys.reduce((arr, key) => { // iterate the `keys` array with Array.prototype.reduce()
arr.push(data[key].meta.total_tweet_count); // push next tweet count onto the array
return arr; // return `arr` to be used in the next iteration
}, []); // start the reduction with empty array
return totalCounts; // [1, 20, 8]
};
In practice, you would:
pass data to the function
avoid intermediate variables
const getData = (data) => {
return Object.keys(data).reduce((arr, key) => {
arr.push(data[key].meta.total_tweet_count);
return arr;
}, []);
};

Related

Async fetch and trouble a copy array in class

I have a problem... thant's a code:
class Currency {
cosnstructor() {
this.currencyInfo = [];
}
getCurrency(getInfo) {
this.currencyInfo = getInfo;
}
}
const actuallyCurrency = new Currency;
(async () => {
const response = await fetch(`http://api.nbp.pl/api/exchangerates/tables/A`);
const data = await response.json();
const currency = data[0].rates;
currency.map(element => curArr.push(element));
})();
const curArr = [];
actuallyCurrency.getCurrency(curArr);
this code working good, but I need in this.currencyInfo a new array, not reference to array curArr.
I this this is what you want:
class Currency {
constructor() {
this.currencyInfo = [];
}
getCurrency(getInfo) {
this.currencyInfo = [...getInfo]; // <-- change this line
}
}
const actuallyCurrency = new Currency;
(async () => {
const response = { json: () => { return [{rates:{a:1, b:2, c:3}}];}};
// const response = await fetch(`http://api.nbp.pl/api/exchangerates/tables/A`);
const data = await response.json();
const currency = data[0].rates;
for(key in currency) curArr.push(currency[key]);
actuallyCurrency.getCurrency(curArr);
console.log(actuallyCurrency.currencyInfo);
})();
const curArr = [];
Some thing for you to understand:
1-... is an operator that does a shallow copy of it's argument. So using as above you'll get a new array in currencyInfo.
2-Why actuallyCurrency.getCurrency(curArr); console.log(actuallyCurrency.currencyInfo); have to be inside the function ?
because os the async nature of the operation. Asyncs are postponed to when the execution has finished so the execution arrives in actuallyCurrency.getCurrency(curArr) BEFORE curArr is populated. This makes the internal currencyInfo array being null and not being populated again after execution.
3-Why this currency.map(element => curArr.push(element)); doesn't work ?
Because currency is an object, not an iterable array. If you want to iterate the elements of an object you have to options: get it's keys as an array, iterate this array and then get the value using it's key OR using for...in as I did.
Hope this is enough. Fell free to ask any question you'd like
There are a few improvements to be made. Probably the most important is arranging to check the currencyInfo instance variable after the fetch completes. This and other suggestions indicated by comments...
class Currency {
cosnstructor() {
this.currencyInfo = [];
}
// methods that assign (and don't return anything) ought to be called "set" something
setCurrency(array) {
this.currencyInfo = array;
}
// it probably makes sense to have this class do it's own async initialization
async fetchCurrency() {
const url = `http://api.nbp.pl/api/exchangerates/tables/A`;
// try/catch, so we can respond to failures
try {
const response = await fetch(url);
const data = await response.json();
// no need to map and not sure why the array needs to be copied. I suspect
// it doesn't but [...array] copies array
this.setCurrency([...data[0].rates]);
} catch (error) {
console.log('error fetching', error);
}
}
}
// instantiation requires ()
const actuallyCurrency = new Currency();
// no async/await at the top level
actuallyCurrency.fetchCurrency().then(() => {
console.log(actuallyCurrency.currencyInfo);
})

How to make multiple requests?

I have an array of data and need to fetch data for each item and combine them.
// This is inside a Nextjs async API route
const data = ['item1','item2','item3']
let result = []
data.forEach(async (item)=>{
const res = await fetch(`https://websitename.com/${item}`)
result = [...result,...res]
}
console.log(result) //gives an empty array
Here, it returns an empty array even though for each item data is being fetched. How to make such requests?
}
Your code should be throwing errors I think? You're trying to declare result twice, but as the second time is within the forEach loop, it's actually a different variable, and is created and destroyed for each loop.
Assuming your fetch works, this should work:
const data = ['item1','item2','item3']
let result = []
data.forEach(async (item)=>{
const res = await fetch(`https://websitename.com/${item}`)
result = [...result,...res]
}
console.log(result)
May this could help you :)
async function doRequest(data) {
// process here
}
const requests = ['item1', 'item2', 'item3'];
const results = requests.map(async (val) => {
const response = await doRequest();
return response;
});
await Promise.all(requests);
Change:
const res = await fetch(`https://websitename.com/${item}`)
const result = [...result,...res]
To:
const response = await fetch(`https://websitename.com/${item}`);
const data = response.json(); // if your response is Json
result.push(data);
result should be const instead of let
Method .forEach() makes sync iteration. It means that an iteration does not wait resolving of your async callback, but just starts it. As a result forEach starts all requests and jumps to line with console.log(result). But at the moment none of the requests are done and array is still empty. If you wrap like that setTimeout(()=>console.log(result),3000) you will see that the array is filled with data.
If you want to make sequential calls:
(async function() {
const data = ['item1','item2','item3']
let result = []
for await (const item of data) {
const res = await fetch(`https://websitename.com/${item}`)
console.log(item);
result.push(item)
}
console.log(result)
})();
If you want to make parallel calls:
(async function() {
const data = ['item1','item2','item3']
let result = await Promise.all(data.map(async (item)=>{
return await await fetch(`https://websitename.com/${item}`)
}))
console.log(result)
})();

How to access the values inside resolved promises?

I'm having difficulty accessing the values from getPeople(0,4).
function getPeople(start, end) {
const peopleArray = [];
for (let i = start; i <= end; i++) {
peopleArray.push(
axios.get(`https://www.testsite.net/api/test/workers/${i}`)
);
}
return peopleArray;
}
useEffect(() => {
Promise.all([getData(), getPeople(0, 4)]).then(item => {
//console.log(item[0].data.orders); //
setData(item);
setPersonData(item);
});
}, []);
item[0] works fine.
Here's the result I'm getting when I console.log(item[1]) How can I access the data?
item[1] is an array of promises.
You simply need to spread the array returned from getPeople() like so:
Promise.all([getData(), ...getPeople(0, 4)]).then(item => {
console.log(item);
});
Promise.all() expects an array of Promise, you were passing an array containing another array.
The getPeople function returns an array of promises.
If you want to await those promises in the Promise.all call, one option is to:
useEffect(() => {
Promise.all([getData(), ...getPeople(0, 4)]).then(item => {
//console.log(item[0].data.orders);
console.log(item[1]); // this will effectively output the people 0 object
setData(item);
setPersonData(item);
});
}, []);
The above will receive as item[0] the resolved value from the promise of getData (which sounds expected already). Then item[1] through item[5] will be the 5 people objects you seem to be expecting.
This is can be made more readable using an async function in useEffect
//make sure this actually returns the people rather than a load of promises
async function getPeople(start, end) {
const peopleArray = [];
for (let i = start; i <= end; i++) {
let person = await axios.get(`https://www.hatchways.io/api/assessment/workers/${i}`);
peopleArray.push(person);
}
return peopleArray;
}
//then in useEffect you need to create another async function
useEffect(() => {
async function getDataAndPersons() {
const data = await getData();
setData(data);
const people = await getPeople(0, 4);
people.forEach(person => setPersonData(person));
};
getDataAndPersons();
}, []);

Why is there not one array with all the items in it, but an array for every item?

I want to push all the item-data into one array. However, when I try, it just makes an array for every item.
async function final() {
const response = await fetch('/api');
const data = await response.json();
console.log(data)
for (item of data) {
const num = item.atomicNumber;
const arr = []
arr.push(num)
console.log(arr)
}
}
Because you are creating a new array inside the loop. Analyzing your own code step by step and understanding what every line does is mandatory to be a good developer :-). Let's analyze it:
async function final() {
const response = await fetch('/api');
const data = await response.json();
console.log(data)
// Until here everything is fine
// You define a loop which will run from this point each time
for (item of data) {
const num = item.atomicNumber;
// You are creating a new array inside the loop. The loop, as its name says, will run one time per item.
const arr = []
arr.push(num)
console.log(arr)
}
}
To fix this, just move the array outside the loop, so it only runs one time:
async function final() {
const response = await fetch('/api');
const data = await response.json();
console.log(data)
// We create the array outside the loop
const arr = []
// Then define a loop which will run from this point each time
for (let item of data) { // Don't forget to define variables with the proper keyword (in this case, "let" is enough).
const num = item.atomicNumber;
arr.push(num)
}
// We log the array when the loop has ended, so it logs only one time
console.log(arr)
}
you need to put your empty array const arr = [] out of the loop, otherwise you're re-declaring the array at every iteration.
async function final() {
const response = await fetch('/api');
const data = await response.json();
console.log(data)
const arr = []
for (item of data) {
const num = item.atomicNumber;
arr.push(num)
}
console.log(arr)
}
You're re-declaring arr in every iteration of the loop. Move the declaration out of it, and just push into it every iteration:
async function final() {
const response = await fetch('/api');
const data = await response.json();
console.log(data)
const arr = [] // arr declared outside the loop
for (item of data) {
const num = item.atomicNumber;
arr.push(num) // but used inside it
console.log(arr)
}
}
try this:
async function final() {
const response = await fetch('/api');
const data = await response.json();
console.log(data)
const arr = []
for (item of data) {
const num = item.atomicNumber;
arr.push(num)
}
console.log(arr)
}

Waiting for all firebase call to finish with map function

I'm fetching my user data and the map function is called several times for each user. I want to wait until all data was pushed to the array and then manipulate the data. I tried using Promise.all() but it didn't work.
How can I wait for this map function to finish before continuing?
Needless to say that the array user_list_temp is empty when printed inside the Promise.all().
const phone_list_promise_1 = await arrWithKeys.map(async (users,i) => {
return firebase.database().ref(`/users/${users}`)
.on('value', snapshot => {
user_list_temp.push(snapshot.val());
console.log(snapshot.val());
})
}
);
Promise.all(phone_list_promise_1).then( () => console.log(user_list_temp) )
I changed the code to this but I still get a wrong output
Promise.all(arrWithKeys.map(async (users,i) => {
const eventRef = db.ref(`users/${users}`);
await eventRef.on('value', snapshot => {
const value = snapshot.val();
console.log(value);
phone_user_list[0][users].name = value.name;
phone_user_list[0][users].photo = value.photo;
})
console.log(phone_user_list[0]);
user_list_temp.push(phone_user_list[0]);
}
));
console.log(user_list_temp); //empty array
}
It is possible to use async/await with firebase
This is how I usually make a Promise.all
const db = firebase.database();
let user_list_temp = await Promise.all(arrWithKeys.map(async (users,i) => {
const eventRef = db.ref(`users/${users}`);
const snapshot = await eventref.once('value');
const value = snapshot.value();
return value;
})
);
This article gives a fairly good explanation of using Promise.all with async/await https://www.taniarascia.com/promise-all-with-async-await/
Here is how I would refactor your new code snippet so that you are not mixing promises and async/await
let user_list_temp = await Promise.all(arrWithKeys.map(async (users,i) => {
const eventRef = db.ref(`users/${users}`);
const snapshot= await eventRef.once('value');
const value = snapshot.val();
console.log(value);
phone_user_list[0][users].name = value.name; // should this be hardcoded as 0?
phone_user_list[0][users].photo = value.photo; // should this be hardcoded as 0?
console.log(phone_user_list[0]);
return phone_user_list[0]; // should this be hardcoded as 0?
})
);
console.log(user_list_temp);
Here is a simple example that uses fetch, instead of firebase:
async componentDidMount () {
let urls = [
'https://jsonplaceholder.typicode.com/todos/1',
'https://jsonplaceholder.typicode.com/todos/2',
'https://jsonplaceholder.typicode.com/todos/3',
'https://jsonplaceholder.typicode.com/todos/4'
];
let results = await Promise.all(urls.map(async url => {
const response = await fetch(url);
const json = await response.json();
return json;
}));
alert(JSON.stringify(results))
}
If I understand your question correctly, you might consider revising your code to use a regular for..of loop, with a nested promise per user that resolves when the snapshot/value for that user is available as shown:
const user_list_temp = [];
/*
Use regular for..of loop to iterate values
*/
for(const user of arrWithKeys) {
/*
Use await on a new promise object for this user that
resolves with snapshot value when value recieved for
user
*/
const user_list_item = await (new Promise((resolve) => {
firebase.database()
.ref(`/users/${users}`)
.on('value', snapshot => {
/*
When value recieved, resolve the promise for
this user with val()
*/
resolve(snapshot.val());
});
}));
/*
Add value for this user to the resulting user_list_item
*/
user_list_temp.push(user_list_item);
}
console.log(user_list_temp);
This code assumes that the enclosing function is defined as an asynchronous method with the async keyword, seeing that the await keyword is used in the for..of loop. Hope that helps!

Categories