0

I have a scenario where i need to query multiple collections at once and retrieve the values based on the collection name. I use Promise.all to do so and it works accordingly like so

var dbPromises = [];

  dbPromises.push(
    admin.firestore().collection("collection1").where("user_id", "==", uid).get(),
    admin.firestore().collection("collection2").where("user_id", "==", uid).get(),
    admin.firestore().collection("collection3").where("user_id", "==", uid).get(),
  );

  const promiseConst = await Promise.all(dbPromises);

promiseConst.forEach((qs) => {

        if (qs.size > 0) {
            if (qs.query._queryOptions.collectionId == "collection1") {
                qs.docs.map((doc) => {
                    valuesArr1.push(doc.data().arr);
                });
            } else if (qs.query._queryOptions.collectionId == "Collection2") {
                qs.docs.map((doc) => {
                    valuesArr2.push(doc.data());
                });
            } else if (qs.query._queryOptions.collectionId == "collection3") {
                qs.docs.map((doc) => {
                    valuesArr3.push(doc.data());
                });
            }
    } else {
        return 
    }
    
  });

for (var i=0; i < valuesArr1.length; i++) {
    if (valuesArr1[i].desiredData) {
        console.log('datas from for loop on datas array', valuesArr1[i].desiredData)
        globalVariable += `<img src="${valuesArr1[i].desiredData}">`; 
    }
 }

Once I do this I map the query snapshot I get and am able to retrieve the values up to this point like so

From the first collection I retrieve an array from a firestore document and then the following collections i just retrieve all documents from the collections. This all 'works' in that when I console.log into the functions console the data shows up exactly as expected. It's only when I want to iterate over the data and assign the results to a global variable to use elsewhere that strange behavior occurs.

The console.log shows the desired data in the functions console with no issues, but the output when I interpolate that data into the html and send it off in nodemailer I get the following result

enter image description here

undefined is always the first in the response when i use the += addition assignment operator, but if i just use the = assignment operator there's no undefined but I obviously don't get all the data I'm expecting.

There are no undefined values or documents in the collections that I'm retrieving, I've checked thoroughly and even deleted documents to make sure of it. After days of researching I've come to the conclusion it has to do with the asynchronous nature of the promise I'm working with and the data not being immediately ready when I iterate it.

Can someone help me understand what I'm doing wrong and how to fix it in node?

AdamBrashear
  • 131
  • 12
  • On `promiseConst.map((qs) => {`. Please only use `Array.map` if you actually return something from within the closure. Otherwise use `Array.forEach`. – Frank van Puffelen May 27 '22 at 20:48
  • I have a suspicion you're not handling the asynchronous nature of the code correctly somewhere, but it's hard to be certain with the three separate snippets. Can you reproduce the problem with a single snippet? – Frank van Puffelen May 27 '22 at 20:51
  • @FrankvanPuffelen the three separate snippets are essentially the full code block, i'll edit so it all looks cohesive. – AdamBrashear May 27 '22 at 21:02
  • @FrankvanPuffelen I also have the same suspicion, but all the code I'm doing is shown above now. When I tried to query the same data without using the dbPromises array, i would only get data back from one or two collections but not all of the ones I needed, which led me to where I am now. – AdamBrashear May 27 '22 at 21:07
  • @FrankvanPuffelen Also I used Array.forEach and it still gave the same result. – AdamBrashear May 27 '22 at 21:37
  • That's expected, but using `forEach` is idiomatic for cases like this. You should only use `map` when you assign the return value to something, which you're not doing. --- Is this what you now have really how the code related in your app when you have the problem? If so, how do you use `globalVariable` in the end? – Frank van Puffelen May 27 '22 at 21:53
  • @FrankvanPuffelen Yes, the only difference between my current code and what you see are the collection and globalVariable names. I'd like to reiterate that everything works fine up until I console.log(globalVariable) outside the scope of the for loop. When i do this I get the same output as shown in the screenshot i entered. The data still comes in, but it's preceded by undefined, which is what I'd like to prevent. – AdamBrashear May 27 '22 at 21:55
  • Hi @AdamBrashear , I've tried to reproduce the posted code above but haven't been able to reproduce the `undefined` data from the `globalVariable`. As stated by Frank, how do you use/pass the data to the nodemailer. are you returning the `globalVariable` and using the result asynchronously? e.g.: ``` someAsyncFunctionWithGlobalVariableAsResult() .then((result) => { console.log(result); }) ``` – Marc Anthony B May 30 '22 at 02:50
  • @MarcAnthonyB Hi Marc, I'll show you how it is used here with a smaller code block ```const user = await admin.firestore().collection("user").where("user_id", "==", uid).get() user.docs.map(doc => { var arr = doc.data().arrayWithDesiredData }) const items = arr.map(item => item.desiredData) items.map(item => { globalVariable += item }) console.log('items',globalVariable) ``` when I use the ```globalVariable=item``` operator i get no undefined, however if I use ```globalVariable+=item``` the first value i get is undefined then the data that's actually there. – AdamBrashear May 30 '22 at 02:58
  • I've tried doing this now with promises and using aysnc/await and i get undefined as the first output no matter whether i use a map on the array from firebase or using a for loop or for...of loop. The globalVariable would then go into the html that gets sent in nodemailer – AdamBrashear May 30 '22 at 03:00
  • I've tried the code above but it is still working without `undefined` as the first output. Have you tried to log the `globalVariable` on each loop of addition assignment that it makes? – Marc Anthony B May 30 '22 at 07:45
  • @MarcAnthonyB Yes, after the code in the codeblock in my question, i log it. That's where the undefined value shows up. I'd like to note this doesn't happen on the client at all, but on the backend inside my cloud function in node and also in the output of my email as the screenshot shows. The first value will show undefined, then the rest of my data outputs right after, but only when doing addition assignment in the loop. – AdamBrashear May 30 '22 at 13:49

1 Answers1

1

I figured out a solution to my problem and would like to share it in hopes it saves a future viewer some time.

Before, I was storing the results of the array from Firebase inside a global variable. To save some head scratching I'll post the code again below.

var globalVariableArray = []

var globalVariable

var dbPromises = [];

dbPromises.push(
  admin.firestore().collection("DataCollection").where("user_id", "==", uid).get()
);

const promiseConst = await Promise.all(dbPromises);

promiseConst.forEach((qs) => {
    if (qs.size > 0) {
        if (qs.query._queryOptions.collectionId == "DataCollection") {
       Promise.all(
         qs.docs.map(doc => {
           globalVariableArray = doc.data().arrayWithDesiredData;
         })
       );
    } 
    else {
        return 
    }  
});

globalVariableArray.map(gv => {
  globalVariable += `<p>gv.desiredData</p>` // <--- Right here is where the problem area was
})

var mailOptions = {
    from: foo@blurdybloop.com,
    to: 'bar@blurdybloop.com
    subject: 'Almost but not quite',
    html: `${globalVariable}`
};

The above code give the expected output, but the output would always have undefined first before the data showed. This happened no matter how the array from Firebase was iterated over.

After strengthening my Google-Fu, I worked out the following solution

var globalVariableArray = []

var globalVariable

var dbPromises = [];

dbPromises.push(
  admin.firestore().collection("DataCollection").where("user_id", "==", uid).get()
);

const promiseConst = await Promise.all(dbPromises);

promiseConst.forEach((qs) => {
    if (qs.size > 0) {
        if (qs.query._queryOptions.collectionId == "DataCollection") {
       Promise.all(
         qs.docs.map(doc => {
           globalVariableArray = doc.data().arrayWithDesiredData;
         })
       );
    } 
    else {
        return 
    }  
});

var mailOptions = {
    from: foo@blurdybloop.com,
    to: 'bar@blurdybloop.com
    subject: 'It works!!',
    html: `${globalVariableArray.map(dataIWantedAllAlong => <p>dataIWantedAllAlong.desiredData</p> )}` <--- Here I simply loop through the array inside the interpolation blocks and voila! no more undefined showing up in the results
};

I perform the loop inside the brackets where I interpolate the dynamic data and am no longer getting that pesky undefined showing up in my emails.

Safe travels and happy coding to you all!

AdamBrashear
  • 131
  • 12