Accessing firestore data at a global scope - node.js

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.

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) => {...}));

Using multiple awaits within a Promise that are dependent on each other

Before describing the issue in a very complex way, I would like to know how to execute multiple dependent awaits in a return Promise one after another without getting new data in my return Promise block. In other words, I just want my try-block to be executed as one statement.
const handler = (payload) => {
return new Promise(async (resolve, reject) => {
try {
const exists = await getRedis(payload)
if(exists === null) {
await setRedis(payload)
await write2Mongo(payload)
resolve()
} else {
resolve()
}
} catch (err) {
reject(err)
}
});
};
In concrete terms, it's about RabbitMQ ("amqplib": "^0.8.0"), where the payloads fly in. These I want to check first if they are known by the system. If not, I want to set them in Redis ("async-redis": "^2.0.0") and then write them to MongoDB ("mongoose": "^6.0.9"). Since I get a lot of messages from RabbitMQ, it works fine at first and then I get a "duplicate key error" from Mongo. This is because my first getRedis returns a null. While writing the data into Redis and MongoDB, a second message comes into my block and gets a "null" value from getRedis, because the message was not yet set via setRedis.
As I read, this is an antipattern with bad error handling. But the corresponding posts have unfortunately not solved my problem.
Can you please help me.
in senario that you describe, you want a queue that you can process it in series
let payloads = [];
const handler = payload => payloads.push(payload);
;(async function insertDistincPayloads() {
for (let i=0; i < payloads.length; i++) {
const exists = await getRedis(payload)
if(exists === null) {
await setRedis(payload)
await write2Mongo(payload)
}
}
payloads = []
setTimeout(insertDistincPayloads, 100); // loop continuously with a small delay
})();
sorry for my bad english :-)

retrieve Firestore document from onCreate trigger with Cloud Functions

I need to retrieve information from a Firestore Document when another document is created. When I try to do this I get hit with an error about the function not being async. It has been so long since I used javascript I am basically a novice again and have no idea how to fix this.
ok, so I am using Firebase Cloud Functions and the function in question is a Firestore .onCreate() trigger.
When the function is triggered I set a sender variable (which is the document ID from a different collection that I need to retrieve)
then I try to get the document as per the documentation.
The function ends up like this:
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}')
.onCreate((snap, context) => {
// when friend request is created
data = doc.data()//get request data
sender = data["sender"]//get request sender from data
const requestRef = db.collection('User').doc(sender);
const doc = await requestRef.get();//get user data of sender
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
});
when I run this in the emulator I get this error:
const doc = await requestRef.get();//get user data of sender
^^^^^
SyntaxError: await is only valid in async functions and the top level bodies of modules
I have absolutely no idea where to go from here.
Can anyone help me with this?
Thanks
The await keyword is valid only in an async function.
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}')
.onCreate(async (snap, context) => {
// ^^^^^
})
If you are (or need to) use synchronous function then you would have to use promise chaining.
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}')
.onCreate((snap, context) => {
return requestRef.get().then((snapshot) => {
if (snapshot.exists) { ... }
})
})
Apart from that, the order of variables/statements looks incorrect. With the current code (as in original question), you may end up getting an error: "Cannot access 'doc' before initialization" Try refactoring it like this:
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}')
.onCreate(async (snap, context) => {
// accessing data from newly created doc
const newDocData = snap.data()
// const sender = "" // ??
const requestRef = db.collection('User').doc(sender);
const doc = await requestRef.get();//get user data of sender
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
})
Where is the sender coming from? I've just commented it above but if the sender is present in new document then you can access it by: const sender = newDocData.sender
If your using await you have to specify that function is asynchronous. Otherwise it will throw error.
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}').onCreate(async (snap, context) => {
// when friend request is created
data = doc.data()//get request data
sender = data["sender"]//get request sender from data
const requestRef = db.collection('User').doc(sender);
const doc = await requestRef.get();//get user data of sender
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
});
Yet some of your references is unknown to us. Maybe this code is not completed.
The main point is you need to understand when you can access async/await or Promise
All await methods must be inside an async block or be handled in an async manor using .then() promises
in this case, the parent function is on this line .onCreate((snap, context) => {
simply inserting an async at the start of the variables will upgrade the arrow function to an async arrow function
.onCreate(async (snap, context) => {

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

Synchronously iterate through firestore collection

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

Resources