Loading data from Firebase asynchronously - javascript

I am developing a business manager web app in React with Firebase back-end. I am also coding a local API to simplify Firebase functions. I created this method, which loads data from a Firebase collection and returns an array of documents.
getDocuments(collection) {
var documents = [];
firebase.firestore().collection(collection).get().then(snapshot => {
snapshot.forEach(doc => {
documents.push(doc);
});
}).then(() => {
return documents;
})
}
However, when I call the method and assign it to a variable which I later print to the console, it says undefined.
var employees = getDocuments("employees");
console.log(employees);
What I want to do is to use .then() after calling the method to print the already loaded data to the console. Something like this:
var employees = getDocuments("employees").then(response => {
console.log(response);
})
Any help would be appreciated. Thank you.

Your getDocuments function seems to be unnecessarily complicated. This:
getDocuments(collection) {
return firebase.firestore().collection(collection).get().then(snapshot=>snapshot.docs)
}
yields exactly the same intended result (an Array of docs wrapped in a promise) as your function but performs faster since it skips looping through all the documents in the snapshot https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot#docs
Afterwards just extract the value from the Promise returned by this function in your preferred way:
Option 1 (async/await)
let employees= await getDocuments('employees')
console.log(employees)
Option 2 (chaining)
let employees =[]
getDocuments('employees').then(response => {
employees=response
console.log(employees)
})
Explanation
When you are doing this:
var employees = getDocuments("employees").then(response => {
console.log(response);
})
you aren't receiving any value from getDocuments since you didn't return anything in the first place.
getDocuments(collection) {
var documents = [];
firebase.firestore().collection(collection).get().then(snapshot => {
snapshot.forEach(doc => {
documents.push(doc);
});
}).then(() => {
return documents; <-- this is returning the value of documents to the parent scope which is 'getDocuments', since the 'then' is related to the 'get' function
})
}

You should assign employees in the then like this
var employees = [];
getDocuments("employees").then(response => {
employees = response;
console.log(response);
})
Or if you are in an asynchronous function, you could go for something like this
var employees = await getDocuments("employees");
console.log(employees);
But the await keyword has to be done in an async function

Related

Synchronize API calls in a for loops VueJS

I am trying to get synchronized flow inside a for loop which each loop having to call a API.
The flow is summarized as follows:
A Demo1() function containing forloop executes one by one, however in each loop it does a API call.,
A MainFunction() retrieves after the for loop has executed and does the final API call.
Supporting API calls for the same function is shown in code and seems quite self explanatory
The code structure is as follows:
</script>
...
async MainFunction() {
if (dataPresent) {
let clientFieldDetails = await this.Demo1()
.then(() => {
//This does the final API call based on the results fetched from for loop
this.processFinalAPICall();
})
.catch(err => {
console.log("Something went wrong... ", err);
});
} else {
console.log(
"No data.."
);
}
},
async Demo1() {
//Traversing around each fieldInfo
//this.dataPresent.items.forEach(item => {
//Replacing with normal for loop
for(item of this.dataPresent.items){
if (item.model) {
//Upload item model API Call
this.uploadItem(item.model)
.then(response => {
//PUT API Call
this.putItemModel().then(response => {
var result = response.data;
//Add itemModel fetched from API response
models.push(result);
console.log(result)
});
})
.catch(err => console.log("Axios err: ", err));
} else {
//Inside item price details
console.log("inside item price");
//API call for fetching price info
this.getitemPriceInfo(item.id);
}
});
},
getitemPriceInfo(itemid){
if(itemid){
//API call for fetching price info
this.getPriceinEuro(itemid);
itemsPrice.push(result)
}
else{
this.getPriceWorldWide().then(response => {
if(response.data === "item not created")
//Create new item API call
this.createNewItem(item.id)
else{
var result = response.data;
//Fetched API response for itemsPrice
itemsPrice.push(result);
console.log(result);
//Update item API call
this.updatePriceItem(item.id);
}
});
}
},
//EDIT: Adding API call
async getPriceinEuro(itemId) {
await this.$axios
.get("/auth/getEuroPrice", {
params: {
itemID: itemId
}
})
.then(response => {
console.log(" Resp :" + response.data);
let result = response.data;
//This gives me the price
itemsPrice.push(result)
});
},
processFinalAPICall(){
//get itemsPrice and models price pushed
var apiBodyModel = [];
this.models.forEach(model=>{
var uri = {
url: /modelInfo/+model
}
apiBodyModel.push(uri)
})
var apiBodyPrice = [];
this.itemsPrice.forEach(price=>{
var uri = {
url: /priceInfo/+price
}
apiBodyPrice.push(uri)
})
//Create a post body request from above data and POST
....
}
...
</script>
The code currently loops in for loop and doesnt wait for the API calls to finish. It executes processFinalCall() first and then the API calls. I am not sure about async/await, if I used it wrong please excuse. How do i get the the forLoop executed first and then the initiating the processFinalAPICall() from MainFunction?
Please let me know if I am doing it the right way. Thanks :)
I am using Node version 8.11 due to our project dependencies.
EDITED: Added API function Call for reference
I'm fairly sure that the problem you have lies in your Demo1 function, which, very broadly, looks like this:
async Demo1() {
[ARRAY OF ITEMS].forEach(item => {
this.uploadItem(item)
.then(response => {
// do some stuff
});
.catch(err => /* log errors */);
});
}
You don't await the upload here, so when you call Demo1() in MainFunction(), it'll go through the uploads without waiting for the previous one to finish first. I think the easiest way to get around this would be to use a for-of loop instead, since you pass a function to .forEach, and that just complicates things.
So instead of [ARRAY OF ITEMS].forEach(item => { ..., you can do this:
async Demo1() {
for(let item of [ARRAY OF ITEMS]) {
await this.uploadItem(item);
}
}
And your modified Demo1 function would look like this:
async Demo1() {
//Traversing around each fieldInfo
for (let item of this.dataPresent.items) {
if (item.model) {
//Upload item model API Call
await this.uploadItem(item.model)
//PUT API Call
let response = await this.putItemModel();
var result = response.data;
//Add itemModel fetched from API response
models.push(result);
console.log(result)
} else {
//Inside item price details
console.log("inside item price");
//API call for fetching price info
this.getitemPriceInfo(item.id);
}
};
}
Note that I haven't tested this, because I don't have the full code to plug it into, so I can't fully guarantee that it'll work. If it doesn't, let me know, so I can help fix it
Here's a bad mockup of this solution.
I guess its a simple change over from what #marsnebulasoup added in his mockup solution. From your comments I tried to replace upload method with axios call. You are missing return keyword after the function call , i.e,
upload(item) {
console.log("Iten value:"+ item);
return axios.get("https://jsonplaceholder.typicode.com/posts/" +item)
.then(response => {
console.log("Done processing API call (Times:"+ item)
console.log(response.data);
})
.catch(console.log);
A simple code changeover is designed based on previous answer by #marsnebulasoup in Replit:
EDIT: Added correct reference:
Code Reference: https://replit.com/join/comkahdm-gagangowda89
Cheers.

issue with Mongoose assigning JSON to variable

Thank you in advance for anyone that reads this. I really appreciate any and all help.
so this is my first app personal app. I have setup a DB in Atlas on mongodb.com and I can write to it with out an issue. But when my app tries and pull from my db I can get it to print to console. But I am able to assign the data to any variable to use anywhere else in my my app.
here is my code that works to print to the console. But not sure what setup or package I am missing so i can store it as a local variable. I am using a callback function to return the api call and the console print out works. just dont know what to do next
function getTerms() {
let allTerms = []
termAdd.find({}, '_id', (err, term) => {
term.map((term) => {
allTerms.push(term)
// if I understand Push() correctly this should store my output to allTerms.
});
//this works to print out to console in JSON.
console.log(allTerms, 'getTerms')
return allTerms;
});
};
if (res.statusCode === 200) {
//callback function to return the console from the function
getTerms()
// when i do a let foo = getTerms() it will return undefined
//so I am really dont understand how to assign the return from
//the function to a variable.
//How do I assign this console output to variable to use for output
console.log('200 statusCode')
};
//Random Number Generator
let ranNum = Math.floor((Math.random() * 10) + 1);
console.log('random number = ',ranNum);
res.render('fs', {
flashCard : 'Test Card',
items: ranNum
});
});
I have the full code on GitHub if that would help I can link to this project.
Should I be using a delay in anyway. I was given a suggestion. But I did not understand what I was reading.
Before anything, please abstain of pushing your .env file to Github or sharing anything in it publicly.
You separated the functionality of getting terms into its own function, which is good:
// Get function for all items in mongodb
function allItems(all) {
let allTerms = []
termAdd.find({}, 'term', (err, term) => {
term.map((term) => {
allTerms.push(term)
});
//this works to print out to console.
console.log(allTerms, 'function allTerms')
//Question is how to I get this JSON to save to a VAR or be passed to another function
});
};
First of all I would rename it to getAllTerms, since it's what it does. And it seems the all parameter is not necessary.
**
Anyway, usually, what you would do is simply return the allTerms variable as such:
// Get function for all items in mongodb
function getAllTerms() {
let allTerms = []
termAdd.find({}, 'term', (err, term) => {
term.map((term) => {
allTerms.push(term)
});
//this works to print out to console.
console.log(allTerms, 'function allTerms')
//Question is how to I get this JSON to save to a VAR or be passed to another function
});
return allTerms
}
However, this wouldn't work because since you are making a call to a database, it might take some time for the database to get the terms; in this case, allTerms might return an empty array, [].
What you have to do is wait for the database to return the terms, push them into the allTerms array, and finally return it.
// Get function for all items in mongodb
async function getAllTerms() {
let allTerms = []
const fetchedTerms = await termAdd.find({}, 'term')
fetchedTerms.forEach(fetchedTerm => allTerms.push(fetchedTerm))
return allTerms
}
If you don't know what async and await are, no worries, here is a good article explaining the why and when to use them.
If you still have any questions, let me know.
Using Async/Await the code above can be optimized as the code below:
`
async function getTerms() {
//using async/await
try{
const terms = await termAdd.find({}, '_id')
const allTerms = terms.map((term)=>term)
// return allTerms here
return allTerms
}catch(err){
throw err
}
}
`
for more insight on Async/Await here is a good read

Async Promise returns undefined or zone aware promise

When calling a function that returns a promise, comes back as undefined unless async operators are removed, then returns ZoneAwarePromise, but contains no data.
I know the query returns data when the function executes, it however does not seem to pass that data to the actual return part of the function call.
I have looked at several Stack questions that have not answered this question including this question:
Async/Await with Request-Promise returns Undefined
This is using a REST endpoint to pull data, the console.logs do show the data is correct, however return comes back as undefined
this.allPeople.forEach(async person => {
const dodString = await this.getRelatedRecords(person); //undefined
}
This is the main function that returns a promise / data
async getRelatedRecords(person) {
// function truncated for clarity
// ...
//
console.warn('This async should fire first');
selPeopleTable.relationships.forEach(relationship => {
allRelationshipQueries.push(
arcgisService.getRelatedTableData(
selPeopleTable.url, [person[oidField.name]], relationship.id, relationship.name),
);
});
await Promise.all(allRelationshipQueries).then(allResults => {
console.log('Inside the Promise');
// The Specific node I am looking for
const data = allResults[1].results.relatedRecordGroups[0].relatedRecords[0].attributes.dod;
console.log(data); // Shows correctly as the data I am looking for
return data;
}).catch(function(data){
console.log('there might be data missing', data);
});
}
Removing the ASYNC operators cause the getRelatedRecords() to fire after the containing function and / or return a 'ZoneAwarePromise' which contains no data. I need getRelatedRecords() to fire first, then to run the rest of the code.
I can provide more snippets if need be.
Zone Aware Promise
When the Async operators are (I think) setup correctly
You need to return this as well:
await Promise.all(allRelationshipQueries).then(allResults => {
console.log('Inside the Promise');
// The Specific node I am looking for
const data = allResults[1].results.relatedRecordGroups[0].relatedRecords[0].attributes.dod;
console.log(data); // Shows correctly as the data I am looking for
return data;
})
return in the above block is returning but all of this is in the scope of the arrow function which is then(allResults => { so you also need to return this function like this:
return await Promise.all(allRelationshipQueries).then(allResults => {
Approach #2:
Second way would be to store that into variable like this:
let dataToReturn = await Promise.all(allRelationshipQueries).then(allResults => {
console.log('Inside the Promise');
// The Specific node I am looking for
const data = allResults[1].results.relatedRecordGroups[0].relatedRecords[0].attributes.dod;
console.log(data); // Shows correctly as the data I am looking for
return data;
}).catch(function(data){
console.log('there might be data missing', data);
});
return dataToReturn;

Removing nested api call from for loop

i made an api call to return a set of data(ex: users - list type, with the returned data i created a for loop and and within my for loop i make another api call to get the user's profile detail based on the user's id. I know this isn't the best practice and i was wondering how i could go about refactoring it.
api.get(...).then(response => {
this.users = response;
for(let i=0; i<this.users.length; i++){
api.get(...this.users[i].id).then(response => {
if(response.name == this.users[i].name)
this.newList.push(response);
})
}
})
and in my html i loop over this.newList to display the info that i need.
How can i remove the nested api call from within the for loop and still get the same results?
One possible solution is to use async/await. Although this will not remove nested loop, but make code look better
Example
async function getUsersAndProfiles () {
try {
this.users = await api.get(...);
for(let i=0; i<this.users.length; i++){
let response = await api.get(...this.users[i].id);
if (response.name == this.users[i].name)
this.newList.push(response);
}
}
catch (e)
console.log(e);
}
You can even move api call for user profile to another async function for possible future reuse and better code structure
Just push array of request into array after that we can use Promise.all() to make a request at a time, then we could create newList based on results array.
api.get(...).then(response => {
this.users = response;
const promises = this.users.map(user => api.get(...user.id))
const result = Promise.all(promises).then(result => {
this.newList = results.filter((user, i) => user.name === this.users[i].name)
})
})

A cleaner way to query a collection and return the data as a list?

It seems like there is a cleaner and more optimised way to query a Firestore collection, call doc.data() on each doc, and then return an array as result. The order in which the docs are pushed into the result array feels haphazard.
There are many steps to this code:
A new result variable is created
A query is made to retrieve the 'stories' collection
For each doc, we call the doc.data()
We push each doc to the result array
Return the result array
function getStories() {
var result = [];
db.collection('stories').get().then(querySnapshot => {
querySnapshot.forEach(doc => result.push(doc.data()));
})
return result;
}
The code works fine but it seems like we can write this code in a cleaner way with less steps.
The code you shared actually doesn't work, as George comment. Since Firestore loads data asynchronously, you're always returns the array before the data has been loaded. So your array will be empty.
In code:
function getStories() {
var result = [];
db.collection('stories').get().then(querySnapshot => {
querySnapshot.forEach(doc => result.push(doc.data()));
})
return result;
}
var result = getStories();
console.log(result.length);
Will log:
0
To fix this, you want to return a promise, which resolves once the data has loaded. Something along these lines:
function getStories() {
var result = [];
return db.collection('stories').get().then(querySnapshot => {
querySnapshot.forEach(doc => result.push(doc.data()));
return result;
})
}
So this basically added two return statements, which makes your result bubble up and then be returned as the promise from getStories. To invoke this, you'd do:
getStories().then(result => {
console.log(result.length);
})
Which will then log the correct number of results.
First, define a map function for use with firebase (named mapSnapshot because it is meant specifically for use with firebase):
const mapSnapshot = f => snapshot => {
const r = [];
snapshot.forEach(x => { r.push(f(x)); });
return r;
}
Then, you can just work with Promise and mapSnapshot:
function getStories() {
return db.collection('stories').
get().
then(mapSnapshot(doc => doc.data()));
}
Usage example:
getStories().then(docs => ... do whatever with docs ...)
To be honest, it isn't less code if used as a one-time solution. But the neat thing here is, it allows to create reusable abstractions. So for example, you could use mapSnapshot to create a snapshotToArray function which can be reused whenever you need to convert the DataSnapshot from firebase to a normal Array:
const snapshotToArray = mapSnapshot(x => x);
Please note: This doesn't depend on any collection name whatsoever. You can use it with any DataSnapshot from any collection to convert it into an Array!
That's not all. You can just as easily create a function from the contents of a DataSnapshot into a regular Array with the contents in it:
const readDocsData = mapSnapshot(x => x.data());
Again, this doesn't look like a big deal – until you realize, you can also create a fromFirebase function, which allows to query various datasets:
const fromFirebase = (name, transform = x => x) => {
return db.collection(name).get().then(transform);
}
And which you can then use like this:
fromFirebase('stories', readDocsData).then(docs => {
// do what you want to do with docs
});
This way, the final result has less steps you as a programmer notice immediatly. But it produced (although reusable) several intermediate steps, each hiding a bit of abstraction.

Categories