Cannot access specific data inside array of objects - javascript

I cannot access the specific objects inside the array. I pushed the data to the array from an api. When i console log countriesArr it shows the full array of objects with all its properties, but when I try to access the specific object in a specific index I get undefined. Also, it should return an array but the typeof shows its an object? Can anyone help?
const countriesURL = 'https://restcountries.eu/rest/v2/all';
let countriesArr = [];
async function getCountriesData() {
const response = await fetch(countriesURL);
const data = await response.json();
showCountries(data);
}
function showCountries(countries) {
countries.forEach((country) => {
countriesArr.push(country)
})}
console.log(countriesArr) //this shows array of objects
console.log(countriesArr[0]) //this shows undefined????

const countriesURL = 'https://restcountries.eu/rest/v2/all';
let countriesArr = [];
async function getCountriesData() {
const response = await fetch(countriesURL);
const data = await response.json();
showCountries(data);
}
function showCountries(countries) {
countries.forEach((country) => {
countriesArr.push(country)
})}
getCountriesData();
console.log(countriesArr) //this shows array of objects
console.log(countriesArr[0]) //this shows undefined????
JavaScript is single threaded-- whether it's running in your browser or NodeJS. So when the Browser processes your code, it will process top>down and evaluate variables in the starting file and proceed to put all functions in the callstack.
When the Browser Engine comes across an asynchronous function, it takes a reference to that function and puts that function into a callback queue, and if it's a promise(which async is since it's just a wrapper on promises), it gets put into a "job queue"/"microtask queue". Once the asynchronous function emits a complete event, the Browser Engine takes that data and pushes that function alongside its reference into the callstack to be run after all current tasks/functions in the stack are done completing. The function reference contains the line number so the engine knows where to evaluate the function.
So knowing this, when you get to getCountriesData(), this asynchronous function gets popped off the main thread, and put into a separate queue that contains threads dedicated for networking related tasks, and program execution immediately processes and evaluates the following console logs before the getCountriesData() finishes executing.
The only way for you to be sure you're correctly inserting/manipulating the data you received from the getCountriesData() into the countriesArr is if you're doing all the data related tasks within that function itself OR in then() proceeding it.

your main problem is async fetching of data, so you make request to the server and while it is making, your next code is compeleting
getCountriesData() is loading...
showCountries(countries) complete!
console.log(countriesArr) complete!
console.log(countriesArr[0]) complete!
In this case countries array is empty and when you take [0] element which is not exists, JS return 'undefined'.
Try to use Promise API to avoid this:
const countriesURL = 'https://restcountries.eu/rest/v2/all';
let countriesArr = [];
async function getCountriesData() {
const response = await fetch(countriesURL);
const data = await response.json();
showCountries(data);
}
function showCountries(countries) {
countries.forEach((country) => {
countriesArr.push(country)
})
}
/* this function return special type Promise */
getCountriesData().then(res => {
console.log(countriesArr)
console.log(countriesArr[0])
})

Related

Using getDoc().then() inside of a loop Firebase

I am trying to loop through an array and for each element in an array i want to use getDoc() and then add the data of the document to a new array. Basically to do this:
useEffect(() => {
let items = [];
for(const cart_item of cartItems){
getDoc(doc(getFirestore(), 'tickets', cart_item.ticket_id)).then((ticket_item) => {
items.push({...ticket_item.data()});
});
}
console.log(items);
}, [cartItems])
However, the items array is still empty after a loop where i log it. I think that the issue is that the loop is not waiting for the getDoc() to return something and goes on. How to make it wait for the getDoc() to finish? I have been reading about async, await but i still don't understand how to handle it.
Try refactoring your code with an async function as shown below:
useEffect(() => {
const getItems = async () => {
// fetching all documents by mapping an array of promises and using Promise.all()
const itemsDocs = await Promise.all(cartItems.map(c => getDoc(doc(getFirestore(), 'tickets', c.ticket_id)))
// mapping array of document data
const items = itemsDocs.map(i => i.data())
console.log(items);
}
getItems();
}, [cartItems])
When you call getDoc it needs to make a call to the Firestore server to get your data, which may take some time. That's why it doesn't immediately return the document snapshot, but instead you use a then() callback that the Firestore SDK calls when it gets the data back from the server.
This sort of asynchronous call changes the order in which you code executes, which is easiest to see if we add some logging to the code:
console.log("Before loading data");
for(const cart_item of cartItems){
getDoc(doc(getFirestore(), 'tickets', cart_item.ticket_id)).then((ticket_item) => {
console.log("Got data");
});
}
console.log("After starting data load");
Then you run this code, the logging output i:
Before loading data
After starting data load
Got data
Got data
...
This is probably not what you initially expected, but it perfectly explains why your console.log(items) shows an empty array: by that time none of the calls to items.push({...ticket_item.data()}) have run yet, because the data is still being loaded from the server.
I noticed that Dharmaraj just posted an answer with the code for a working solution using Promise.all, so I recommend checking that out too.

How do I execute these 2 different blocks of code in a sequence in JavaScript?

this is the code from my route:
const hotspots = await Hotspot.find({user: req.user._id});
if(!hotspots) return res.render("hotspot");
let hotspotData = [];
let rewards = [];
function getApiData(){
return new Promise((resolve,reject)=>{
let error = false;
for (let hotspot of hotspots){
axios.get(`https://api.helium.io/v1/hotspots/${hotspot.hotspot}`)
.then((resp)=>hotspotData.push(resp.data));
axios.get(`https://api.helium.io/v1/hotspots/${hotspot.hotspot}/rewards/sum`)
.then((resp)=> {
console.log("first console log",resp.data.data.total)
rewards.push(resp.data.data.total) ;
console.log("Data gained from axios",resp.data.data.total);
})
}
if(!error) resolve();
else reject("Something went wrong");
})
}
getApiData().then(()=> {
console.log(hotspotData);
console.log(rewards);
res.render("hotspot",{hotspots: hotspotData, rewards: rewards});
});
Everything else is fine, but when I'm logging hotspotData and rewards, I'm getting an empty array but as you can see I've already pushed the data I got from APIs to those arrays respectively. But sti;; I'm getting empty array. Please help me. I have logged the API data and yes it's sending the correct data but I don't know why that data does not get pushed to the arrays.
Edit: This is the output which suggests my code is not getting executed in the way I want it to:
What happens here is that the code that sends back the response is executed before the axios responses arrive:
First, a call to a database (is that correct?) using a async/await approach.
Then you define a function that returns a Promise and call it processing the response in the then() function.
That piece of code is executed when the Promise is resolve()d (watch out there's no code to handle a rejected response).
Now the promise is resolved once the for loop ends, it doesn't wait
for the axios promises to return, it just triggers the requests and
continue. The code block after they return will be run in another moment.
If you want to solve this, one thing you could do is to use async/await everywhere, and make the calls sequentially, something like this:
const hotspots = await Hotspot.find(...);
function async getApiData() {
let hotspotData = [];
let rewards = [];
for loop {
hotspot = await axios.get(hotspot)
reward = await axios.get(reward)
...
// add both to the arrays
}
return { hotspotData, rewards }
}
const { hotspotData, rewards } = await getApiData();
console.log(hotspotData);
console.log(rewards);
res.render("hotspot",{hotspots: hotspotData, rewards: rewards});
Note: the code above is more pseudo-code than real javascript ok?
I think this could be an interesting read: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
Also, notice all the axios calls seem to be independent from each other, so perhaps you can run them asynchronously, and wait for all promises to be resolved to continue. Check this out for more info about it: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
(make sure it works sequentially, first)
I think it should be something like an array of promises (one per hotspot and reward in the for loop), then returning something like:
return Promise.all([promise1, promise2, promise3]);

.map async callback and event loop

This code snippet is taken from google developers:
// map some URLs to json-promises
const jsonPromises = urls.map(async url => {
const response = await fetch(url);
return response.json();
});
I understand that at the end jsonPromises is going to be an array of Promises. However, i'm not completely sure how that happens.
According to my understanding, when execution gets to this line const response = await fetch(url); it moves it from call stack to Web Api, does the same to return response.json(); and moves to the next url from urls. Is it correct ? I understand how event loop works when it comes to simple setTimeout , but this example confuses me a lot.
Here's what happens:
1) The .map function runs, and executes the callback for each element (the async function). The function calls fetch, which starts some under the hood magic inside the engine. fetch returns a Promise. The await gets reached, which halts the functions execution. The call to the function done by .map evaluates to a promise. .map collects all those promises and stores them in an array and returns the array.
2) Somewhen, one of the fetches running under the hood has etablished a connection, and calls back into JS and resolves the fetch promise. That causes the async function call to go on. res.json() gets called, which causes the engine to collect all the packets from the connection and parse it as JSON under the hood. That again returns a Promise, which gets awaited, which causes the execution to stop again.
3) Somewhen, one of the connections end, and the whole response is available as JSON. The promise resolves, the async function call continues execution, returns, which causes the promise (the one in the array) to be resolved.
Since async functions return a promise, think of the example as any other promise-returning function:
// map happens synchronously and doesn't wait for anything
const map = (arr, cb) => {
const mapped = [];
for (const [i, element] of arr.entries()) {
mapped.push(cb(element))
}
return mapped;
}
// just return a promise that resolves in 5 seconds with the uppercased string
const promiseFn = url => {
return new Promise(resolve => setTimeout(() => resolve(url.toUpperCase()), 5000))
}
const promiseArray = map(['url', 'url', 'url'], promiseFn)
All the promises returned by the async callback are pushed synchronously. Their states are modified asynchronously.
If on the other hand you're looking for the difference between setTimeout and a Promise vis-a-vis the event loop, check the accepted answer here: What is the relationship between event loop and Promise

javascript promise still executing code synchronously

so basically i have a web application that retrieves data from firebase. and since it takes a lot of time to retrieve data from firebase, i used promise to make my code to populate at the right time. here is my code:
var promise = getDataFirebase();
promise.then(function () {
console.log(Collect);
console.log("firsst");
return getDataFirebaseUser();
}).then(function () {
console.log("Second");
});
function getDataFirebase() {
return new Promise(function (resolve, reject) {
refReview.on("value", function (snap) {
var data = snap.val();
for (var key in data) {
Collect.push({
"RevieweeName": data[key].revieweeID.firstname.concat(" ", data[key].revieweeID.lastname),
"ReviewerName": data[key].reviewerID.firstname.concat(" ", data[key].reviewerID.lastname),
rating: data[key].rating,
content: data[key].content,
keyOfReviewee: data[key].revieweeID.userID
})
var getDataToUsers = firebase.database().ref("users").child(data[key].revieweeID.userID);
getDataToUsers.once("value", async function (snap) {
var fnLn = snap.val();
var first = fnLn.isTerminated;
console.log("terminateStatus", first);
});
}//end of for loop
resolve();
}); //end of snap
});
}
so in function getDataFirebase, data is retrieved from firebase and is push to an array called , Collect. So, after pushing 1 row, it query again to the firebase for another table of data then continues the loop. The problem here is that, i wanted to finish all processes before it resolves the promise.
the output of console according to the code is as follow:
Collect (array)
first
Second
terminateStatus, 1
it must be
Collect (array)
first
terminateStatus,1
second
I'm not 100% sure how your code is working but it looks like you're doing the right thing on refReview.on("value"): creating a promise before calling Firebase, and then resolving it afterwards. But you're not doing that on getDataToUsers.once("value"). There, you're firing the event and not waiting for it to return — the for-loop continues on, and all the callbacks are processed later, but resolve is at the end of the for-loop, so it's too late by then.
My guess is that you thought the async keyword would cause the for-loop to wait for that job to complete, but actually all it does here is cause the callback to return a promise — which is ignored by the on function.
You have a few options, but probably the best will be to use Promise.all, which accepts an array of promises and waits for them all to be resolved — but you should probably use Firebase's Promise API rather than bolting promises onto the event API. So it'll look something like:
refReview.once('value')
.then(snap => Promise.all(snap.val().map(item =>
firebase.database()
.ref("users")
.child(item.revieweeID.userID)
.once('value')
.then(snap => {
console.log('terminated');
])
})));
(except with the actual functionality added in, natch)

Need help related async functions execution flow (await, promises, node.js)

I will appreciate if you help me with the following case:
Given function:
async function getAllProductsData() {
try {
allProductsInfo = await getDataFromUri(cpCampaignsLink);
allProductsInfo = await getCpCampaignsIdsAndNamesData(allProductsInfo);
await Promise.all(allProductsInfo.map(async (item) => {
item.images = await getProductsOfCampaign(item.id);
}));
allProductsInfo = JSON.stringify(allProductsInfo);
console.log(allProductsInfo);
return allProductsInfo;
} catch(err) {
handleErr(err);
}
}
That function is fired when server is started and it gathers campaigns information from other site: gets data(getDataFromUri()), then extracts from data name and id(getCpCampaignsIdsAndNamesData()), then gets products images for each campaign (getProductsOfCampaign());
I have also express.app with following piece of code:
app.get('/products', async (req, res) => {
if (allProductsInfo.length === undefined) {
console.log('Pending...');
allProductsInfo = await getAllProductsData();
}
res.status(200).send(allProductsInfo);
});
Problem description:
Launch server, wait few seconds until getAllProductsData() gets executed, do '/products' GET request, all works great!
Launch server and IMMEDIATELY fire '/products' GET request' (for that purpose I added IF with console.log('Pending...') expression), I get corrupted result back: it contains all campaigns names, ids, but NO images arrays.
[{"id":"1111","name":"Some name"},{"id":"2222","name":"Some other name"}],
while I was expecting
[{"id":"1111","name":"Some name","images":["URI","URI"...]}...]
I will highly appreciate your help about:
Explaining the flow of what is happening with async execution, and why the result is being sent without waiting for images arrays to be added to object?
If you know some useful articles/specs part covering my topic, I will be thankful for.
Thanks.
There's a huge issue with using a global variable allProductsInfo, and then firing multiple concurrent functions that use it asynchronously. This creates race conditions of all kinds, and you have to consider yourself lucky that you got only not images data.
You can easily solve this by making allProductsInfo a local variable, or at least not use it to store the intermediate results from getDataFromUri and getCpCampaignsIdsAndNamesData - use different (local!) variables for those.
However, even if you do that, you're potentially firing getAllProductsData multiple times, which should not lead to errors but is still inefficient. It's much easier to store a promise in the global variable, initialise this once with a single call to the info gathering procedure, and just to await it every time - which won't be noticeable when it's already fulfilled.
async function getAllProductsData() {
const data = await getDataFromUri(cpCampaignsLink);
const allProductsInfo = await getCpCampaignsIdsAndNamesData(allProductsInfo);
await Promise.all(allProductsInfo.map(async (item) => {
item.images = await getProductsOfCampaign(item.id);
}));
console.log(allProductsInfo);
return JSON.stringify(allProductsInfo);
}
const productDataPromise = getAllProductsData();
productDataPromise.catch(handleErr);
app.get('/products', async (req, res) => {
res.status(200).send(await productDataPromise);
});
Of course you might also want to start your server (or add the /products route to it) only after the data is loaded, and simply serve status 500 until then. Also you should consider what happens when the route is hit after the promise is rejected - not sure what handleErr does.

Categories

Resources