Synchronously iterate through firestore collection - node.js

I have a firebase callable function that does some batch processing on documents in a collection.
The steps are
Copy document to a separate collection, archive it
Run http request to third party service based on data in document
If 2 was successful, delete document
I'm having trouble with forcing the code to run synchronously. I can't figure out the correct await syntax.
async function archiveOrders (myCollection: string) {
//get documents in array for iterating
const currentOrders = [];
console.log('getting current orders');
await db.collection(myCollection).get().then(querySnapshot => {
querySnapshot.forEach(doc => {
currentOrders.push(doc.data());
});
});
console.log(currentOrders);
//copy Orders
currentOrders.forEach (async (doc) => {
if (something about doc data is true ) {
let id = "";
id = doc.id.toString();
await db.collection(myCollection).doc(id).set(doc);
console.log('this was copied: ' + id, doc);
}
});
}

To solve the problem I made a separate function call which returns a promise that I can await for.
I also leveraged the QuerySnapshot which returns an array of all the documents in this QuerySnapshot. See here for usage.
// from inside cloud function
// using firebase node.js admin sdk
const current_orders = await db.collection("currentOrders").get();
for (let index = 0; index < current_orders.docs.length; index++) {
const order = current_orders.docs[index];
await archive(order);
}
async function archive(doc) {
let docData = await doc.data();
if (conditional logic....) {
try {
// await make third party api request
await db.collection("currentOrders").doc(id).delete();
}
catch (err) {
console.log(err)
}
} //end if
} //end archive

Now i'm not familiar with firebase so you will have to tell me if there is something wrong with how i access the data.
You can use await Promise.all() to wait for all promises to resolve before you continue the execution of the function, Promise.all() will fire all requests simultaneously and will not wait for one to finish before firing the next one.
Also although the syntax of async/await looks synchronous, things still happen asynchronously
async function archiveOrders(myCollection: string) {
console.log('getting current orders')
const querySnapshot = await db.collection(myCollection).get()
const currentOrders = querySnapshot.docs.map(doc => doc.data())
console.log(currentOrders)
await Promise.all(currentOrders.map((doc) => {
if (something something) {
return db.collection(myCollection).doc(doc.id.toString()).set(doc)
}
}))
console.log('copied orders')
}

Related

forEach loop finishes after the last line of code executes

What I am trying to do here is essentially go through a bunch of saved messages in a database, check if they still exist and if they do not, delete them, so what I've done is it checks if the message exists and if not it pushes it into an array of all the non-existent messages, but the code is running in the wrong order, it runs the if statement before the loop finishes. So for example it would console log the 2 before the 1. Any help would be greatly appreciated.
const {
reactionRoleInformation,
} = require("../database-functions/reactionroles/reactionRoleInformation");
const {
deleteManyReactionRoles,
} = require("../database-functions/reactionroles/deleteManyReactionRoles");
module.exports.cacheMessages = async (bot) => {
const messageArray = await reactionRoleInformation();
let deletedMessagesIds = new Array();
messageArray.forEach(async (message) => {
try {
await bot.guilds.cache
.get(message.guild)
.channels.cache.get(message.channel)
.messages.fetch(message.id);
} catch (err) {
deletedMessagesIds.push(message.uniqueId);
return;
}
console.log(1)
await bot.channels.cache.get(message.channel).messages.fetch(message.id);
});
console.log(2)
if (deletedMessagesIds.length !== 0) await deleteManyReactionRoles(deletedMessagesIds);
};
.forEach() will not wait for async functions. Instead, use a combination of .map with Promise.all.
await Promise.all(messageArray.map(async (message) => {...}));

NodeJs Returning data from async fetch on a variable to reuse

I'v been searching around for a few hours (with no success) on how to have an async function return a result, store it in a variable outside the function and reuse that data.
My issue is that I fetch the same data over and over in a few of my functions which seems unnecessary.
Basically this is what I want, and right now it's returning a promise.
let leads;
try {
var result = await fetch('http://localhost:3000/lds');
leads = await result.json();
return leads;
} catch (e) {
// handle error
console.error(e)
}
}
var results = readDb();
console.log(results);
For example, initially I run a function fetch the data and create a table.
Secondly I run another function that fetches the same data to create pagination buttons.
Thirdly I run another function that fetches the same data, yet again, and listens for the pagination button click to show the data of the corresponding page number.
Ideally I would fetch the data only once.
Thanks in advance!
We can define leads outside the function and check if it's necessary to fetch it:
let leads;
const getLeads = async function() {
try {
if (!leads) { // fetch leads only they weren't already loaded
const result = await fetch('http://localhost:3000/lds');
leads = await result.json();
}
return leads;
} catch (e) {
// handle error
console.error(e)
}
}
Since you are using async/await you need to await the value, however it is only available in an async function. You can reuse a variable outside the function to store the value once it is loaded.
// reuse leads variable
let leads;
// async function to populate/return leads
async function readDb() {
// only populate if it is undefined
if (typeof leads === 'undefined') {
const result = await fetch('http://localhost:3000/lds');
leads = await result.json();
}
// return just loaded or referenced value
return leads;
}
// call the function asynchronously
(async function(){
// await the results here inside an async function
const results = await readDb();
console.log(results);
}());

How to push an object into an array in async function

i have been trying to insert an object into an array in async function ,but it
return an empty array as output in nodejs ,mongoose
var data = [];
app.get("/api/post", async (req, res) => {
const post = await UserPost.find();
post.forEach(async element => {
const email = await element.userid;
const user = await Account.find({ email });
const usern = await user[0].username;
var userobject = {
element,
usern
};
//Promise.all(userobject)
data.push(userobject);
});
console.log(data);
res.send({ data });
});
It seems you are struggling with promises. In order to achieve this specific scenario, you can use Promise.all and Array.map.
Here is a code I edited for you:
(*please note that this is just a dummy code for the sake of explanation)
app.get("/api/post", async (req, res) => {
try {
const posts = await dummyPromiseResolver(); // first promise
const promises = posts.map(async element => {
const user = await dummyEmailReturn(element.userid); // second promise
const usern = user[0].username;
return {
usern,
...element
};
});
const fresult = await Promise.all(promises);
res.send(fresult);
} catch (error) {
console.error("error in posts fetch:" + error);
}
});
If I describe this code, posts.map is creating an Array of promises since we need to iterate through every object in the array and needs to add values from separate promises.
Then Promise.all can execute your promise array and return final results array with your desired results.
Note: You can also use for … of as well but when we need to happen things parallelly we use Promise.all. You can find more information from this thread.
here is a link for code sandbox: https://codesandbox.io/embed/serverless-cookies-nu4h0
Please note that I have added dummyPromiseResolver and dummyEmailReturn which would be equal to UserPost.find() and Account.find() functions respectively. In addition to that, I removed a few unnecessary awaits in your code. I added a try catch block to catch any exceptions. You can change that try catch as you please.
hope this will help you. let me know if you need more clarifications.

Accessing firestore data at a global scope

I'm trying to access the firestore data and push it to an array. This is super basic but for some reason I cannot figure out why this isn't working:
var db = admin.firestore();
let arr = [];
var Ref = db.collection('Test').doc('Document');
var getDoc = Ref.get()
.then(doc => {
if (!doc.exists) {
console.log('No such document!');
} else {
let data = doc.data().Name;
arr.push(data);
}
})
.catch(err => {
console.log('Error getting document', err);
});
console.log(arr) // expecting >>> ['Joe'] (ie: data that is in firestore object)
Why doesn't arr contain the firestore object?
Thanks for the help.
It doesn't contain the data from firestore yet.
The get() operates asynchronously by returning a Promise and then continuing program execution. The next line is the console.log(arr), but arr isn't populated yet - it is populated when the Promise completes (calls the then() part).
If you're using a sufficiently modern version of node (node 8 and up, which you should be using at this point), then you can use await to wait for the asynchronous operation to complete before continuing to the next line.
I haven't tested it, but your code might look something like this after a rewrite:
doc = await Ref.get();
if (!doc.exists) {
console.log('No such document!');
} else {
let data = doc.data().Name;
arr.push(data);
}
console.log(arr)
This will work because the await waits for the async get() to complete and return the doc. The rest of it is processed synchronously.

Async/Await and Then not working in my case

I have a function which contains a thousand of objects in an array:
function Alltransaction(transactionArray) {
transactionArray.map(async (transaction) => {
dataAgainsthash = await web3.eth.getTransaction(transaction)
TransactionObject = {
transactionHash : transaction,
from : dataAgainsthash.from
};
transactionArray.push(TransactionObject)
console.log("transaction array", transactionArray)
});
}
then i have another function which stores these thousands of object array into db
function saveTransactionToDb() {
console.log("after loop",transactionArray)
transactionss = new Transaction({
blockNumber : blockNumbers ,
transactions : transactionArray
})
// Now save the transaction to database
await transactionss.save();
// console.log("save to database")
}
then I call this in my router
await Alltransaction(transactionArray);
await saveTransactionToDb();
and I also try
Alltransaction(transactionArray).then(saveTransactionToDb())
But it always runs saveTransactionToDb() before the array of object populates the Alltransaction() method
have you try the async keyword before saveTransactionToDb and Alltransaction functions??
async function Alltransaction(transactionArray){
// your code
}
async function saveTransactionToDb(){
// your code logic*
await transactionss.save();
}
First, in Alltransaction the promise must be returned as well. In your code the function starts some processes but doesn't not await on it. Also, do not push the promises to the original array. I'm not sure what you were trying to accomplish there. Because mapping over the array gives you an array of promises, you can unify all of them with Promise.all().
function Alltransaction(transactionArray) {
const promises = transactionArray.map(async (transaction) => {
dataAgainsthash = await web3.eth.getTransaction(transaction)
const TransactionObject = {
transactionHash : transaction,
from : dataAgainsthash.from
};
return TransactionObject;
});
return Promise.all(promises);
}
Change saveTransactionToDb to receive an array instead of using the original array.
Then you'll be able to call it as:
const t = await Alltransaction(transactionArray);
await saveTransactionToDb(t);
Your second try it's not correct:
Alltransaction(transactionArray).then(saveTransactionToDb())
It's the same as:
const t = Alltransaction(transactionArray);
const s = saveTransactionToDb();
t.then(s)
That's why saveTransactionToDb doesn't way for transactions to complete. To use then, just pass the function without calling it:
Alltransaction(transactionArray).then(saveTransactionToDb)

Resources