Use async forEach loop while fetching data from firestore - node.js

I have firestore data somewhat like this:
"Support": {
"userid":"abcdxyz",
"message": "hello"
}
I am using nodejs to fetch my data and I also want to show the email address and name of the person who sent this message. So I am using following function:
database.collection("support").get().then(async function (collections) {
var data = [];
console.log("data collected");
collections.forEach(async function (collection) {
var temp = {};
var collectionData = collection.data()
var userInfo = await getUserDetails(collectionData.userId)
temp.name = userInfo.name
temp.supportMessage = collectionData.supportMessage
data.push(temp)
console.log("data pushed")
});
console.log("data posted")
return res.status(200).end(JSON.stringify({ status: 200, message: "Support Message fetched successfully.", data: data }))
}).catch(error => {
return res.status(500).end(JSON.stringify({ status: 500, message: "Error: " + error }))
});
Here the sequence of logs is following: data collected, data posted, data pushed
I want the sequence like this: data collected, data pushed (x times), data posted

Use following code:
database.collection("support").get().then(async function (collections) {
var data = [];
console.log("data collected");
for await(let collection of collections){
var temp = {};
var collectionData = collection.data()
var userInfo = await getUserDetails(collectionData.userId)
temp.name = userInfo.name
temp.supportMessage = collectionData.supportMessage
data.push(temp)
console.log("data pushed")
}
console.log("data posted")
return res.status(200).end(JSON.stringify({ status: 200, message: "Support Message fetched successfully.", data: data }))
}).catch(error => {
return res.status(500).end(JSON.stringify({ status: 500, message: "Error: " + error }))
});
OR
Use can use
var promise = Promise.all(collections.map((collection) =>{
...
return await ... //or a promise
}));
promise.then(() => {
console.log("posted");
return res.status(200).end(...);
})

I solved my answer with the help of #estus comment.
Credit: #estus
var data = [];
var tempCollection = [];
collections.forEach(collection => {
tempCollection.push(collection.data());
});
for (collection of tempCollection) {
var temp = {};
var userInfo = await getUserDetails(collection.userId)
temp.name = userInfo.name
temp.supportMessage = collection.supportMessage
data.push(temp)
}
It solved my problem very easily.

I know this might not the OP's exact use case but if you're interested in compiling the results of a collection query into an array, you can still use the .docs property of a QuerySnapshot to obtain the list of items:
...
const userId = <some-id>;
const usersRef = db.collection(`users`)
const usersSnapshot = await usersRef.where("id", "==", userId).get()
if (usersSnapshot.empty) {
console.log('found no matching user ', userId);
return;
}
console.log(`found ${usersSnapshot.size} user records`);
const userResults = []
// this block here doesn't construct the results array before the return statement
// because .foreach enumerator doesn't await: https://stackoverflow.com/q/37576685/1145905
// usersSnapshot.forEach((doc) => {
// // doc.data() is never undefined for query doc snapshots
// //console.log(doc.id, " => ", doc.data());
// userResults.push[doc.data()];
// });
for (user of usersSnapshot.docs) {
// console.log(user.id, " => ", user.data());
userResults.push(user.data());
}
...
A more detailed example is here

Related

When using forEach in a Cloud Function, I can't make .sendToDevice() method work

I can send messages to the iOS device using the second function shown below.
I get the document id in the collection name "users" which is at the first level and send the message using the token stored in the tokens subcollection therefore admin.firestore().collection('users').doc(userId).collection('tokens').
I have to change the way the function looks for the user. Rather than relying on the document id of the user, I now need a query in order to find the user. Being a query, unless I'm wrong, I'm forced to use forEach in order to send the message to the user. The function now looks as shown immediately below. In essence, once I know I have the user that needs to receive the message, I'm using the original function format to send the message but the message is never sent. All I see in the logs is Firebase messaging error and I have yet to figure out where the mistake is.
exports.sendMessage = functions.https.onRequest(async (res, response) => {
const body = res.body;
const orderTotal = body.total;
const orderId = String(body.id);
const query = await usersRef.where('token', '==', token).get();
if (query.empty) {
console.log('No matching documents.');
return;
}
query.forEach(doc => {
const tokens = usersRef.doc(doc.id).collection('tokens');
tokens.get()
.then(snapshot => {
const results = [];
snapshot.forEach(doc => {
const fcmToken = doc.data().fcmToken
console.log("fcmToken =>", fcmToken);
results.push(fcmToken);
})
const payload = {
notification: {
title_loc_key: 'notify_title',
subtitle_loc_key: 'notify_subtitle',
body_loc_key: 'notify_body',
badge: '1',
sound: 'cha-ching.caf',
mutable_content: 'true'
},
data: {
'total': orderTotal,
'orderId': orderId
}
}
response.send([results, , payload])
admin.messaging().sendToDevice(results, payload).then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
return { success: true };
}).catch((error) => {
return { error: error.code };
})
})
.catch(err => {
console.log("Error getting documents", err);
});
});
});
This is the original function which I used when using the document id.
exports.sendMessage = functions.https.onRequest(async (res, response) => {
const body = res.body
const orderTotal = body.total
const orderId = String(body.id)
const tokenReference = admin.firestore().collection('users').doc(userId).collection('tokens')
const tokenSnapshots = await tokenReference.get()
const results = []
tokenSnapshots.forEach(tokenSnapshot => {
const fcmToken = tokenSnapshot.data().fcmToken
results.push(fcmToken)
})
const payload = {
notification: {
title_loc_key: 'notify_title',
subtitle_loc_key: 'notify_subtitle',
body_loc_key: 'notify_body',
badge: '1',
sound: 'cha-ching.caf',
mutable_content: 'true'
},
data: {
'total': orderTotal,
'orderId': orderId
}
}
response.send([results, , payload])
admin.messaging().sendToDevice(results, payload).then((response) => {
console.log('Successfully sent message:', response);
return { success: true };
}).catch((error) => {
return { error: error.code };
})
})
Screenshot of the error:
The onRequest() function terminates when you return a response. You are using sendToDevice() after response.send(). Also make sure you are handling all the promises correctly. Try refactoring the using async-await syntax as shown below:
exports.sendMessage = functions.https.onRequest(async (res, response) => {
try {
const body = res.body;
const orderTotal = body.total;
const orderId = String(body.id);
const query = await usersRef.where("token", "==", "TOKEN").get();
if (query.empty) {
console.log("No matching documents.");
return;
}
// Query tokens of all users at once
const tokenSnapshots = await Promise.all(
query.docs.map((user) => usersRef.doc(user.id).collection("tokens").get())
);
// Array of all fcmTokens
const results = tokenSnapshots.reduce((acc, snapshot) => {
acc = [...acc, ...snapshot.docs.map((doc) => doc.data().fcmToken)];
return acc;
}, []);
const payload = { ...FCM_PAYLOAD };
const fcmResponse = await getMessaging().sendToDevice(results, payload);
console.log("Successfully sent message:", fcmResponse);
response.send([results, , payload]);
} catch (error) {
console.log(error);
response.json({ error: "An error occured" });
}
});
Also checkout Terminating HTTP Cloud Functions.
After days of working on this, it turns out there wasn't anything wrong with the function. I don't know how VPN works but the fact that I had it enabled on my iPhone was the reason I wasn't getting the notification.
I paused the VPN and the notification was received.

Unable to map inside an async function

In the below code, I am fetching data from an external api. After parsing the data as json, I wanted to map through it and get a modified version.
For some reason, the console.log(jsonData) inside the map function is not getting executed. Please check the code below for clarity
const getRandomOutfit = async (req, res) => {
const { gender, countryCode } = req.params;
if (req.params.gender === "FEMALE" || req.params.gender === "MALE") {
try {
const response = await fetch(URL);
const jsonData = await response.json();
const outputData = jsonData.map((productItem) => {
console.log(productItem); // doesn't get printed
// some operation
return productItem;
});
await res.json(jsonData);
} catch (error) {
res.status(500).send("Error getting data");
}
} else {
res.status(500).send("Invalid category");
}
};
I'm confused about what I am missing here and making an error.
I rewrote the code to make it clearer to understand. In general, it is best to take the fail first approach. Notice, how the first thing I do is return upon failure.
As to why you code is not printing anything out, try printing jsonData. It might be that this is an empty array.
const getRandomOutfit = async (req, res) => {
const { gender, countryCode } = req.params;
if (gender !== "FEMALE" && gender !== "MALE")
return res.status(500).send("Invalid category");
try {
const response = await fetch(URL);
const jsonData = await response.json();
console.log(jsonData); // what does this return?
const outputData = jsonData.map((productItem) => {
console.log(productItem); // doesn't get printed
// some operation
return productItem;
});
await res.json(jsonData);
} catch (error) {
res.status(500).send("Error getting data");
};

How to divide the result into several messages?

For example, the result of a query to the database involves the output of 50 records. These records are issued in a single chat message, which is difficult to analyze. How to divide the result into several messages with 3 records in each?
bot.on('message', async (message) => {
const {text, chat} = message;
const {id: chatId} = chat;
let response = '';
try {
const [rows] = await sequelize.query(`SELECT book FROM books t WHERE (t.*)::text LIKE '%${text}%' LIMIT 3`);
if (rows.length) {
response = rows.map(row => row.book).join("\n");
} else {
response = 'text';
}
} catch (error) {
console.error(error.message);
response = 'book not found';
} finally {
if (response) {
bot.sendMessage(chatId, response);
}
}
})
Chunk rows that came from db to N items by reducing it using splice
Then that chunk to array of book names using map
Convert chunk of book names to message by joining by some logic in our case with newline symbol
Push it to messages array
Finally iterate that array and send
bot.on('message', async (payload) => {
const {text, chat} = payload;
const {id: chatId} = chat;
const BOOKS_PER_MESSAGE = 3;
let messages = [];
try {
const query = `SELECT book FROM books t WHERE (t.*)::text LIKE '%${text}%'`;
let [rows] = await sequelize.query(query);
while(rows.length) {
const chunk = rows.splice(0, BOOKS_PER_MESSAGE); // [1]
const books = chunk.map(row => row.book); // [2]
const message = books.join("\n"); // [3]
messages.push(message); // [4]
}
}
catch (error) {
console.error(error.message);
}
finally {
if (!messages.length) {
bot.sendMessage(chatId, 'Book not found');
return;
}
messages.forEach(message =>
bot.sendMessage(chatId, message)); // [5]
}
});

buffer Array to String and then saving in directory

I am new to React and I am currently facing an issue that I have API written in Express.js in which I am receiving ur fetching whatever it is called an image uploaded from mobile device in a buffer Array and now I have to convert it into string and add extension to it (let say .jpg) and store it into MongoDB Atlas here is my API written in Express ```module.exports.submitFormDataFile = async (req, res) => {
var hkeyArray = [];
const body = req.body;
const collection_name = body.form_collect_name;
const formstructureresult = await FormsStructure.findOne({
collection_name: collection_name
});
formstructureresult.formdata.forEach((eachData) => {
if (eachData.element === 'file') hkeyArray.push(eachData.hkey);
});
// console.log(hkeyArray)
hkeyArray.forEach((element) => {
//In this part I was supposed to convert it in string and save with extension
console.log(req.files[element].data);
});
if (body._id != '' && body._id != null) {
try {
const result = db.collection(
collection_name
);
const results = await result.findOne({
_id: mongoose.Types.ObjectId(body._id)
});
if (!results) {
res.status(200).json({
ERROR: 1,
MESSAGE: 'Invalid Record'
});
} else {
delete body._id;
var newvalues = { $set: body};
const resu = await result.updateOne(results, newvalues);
if (!resu) {
res.status(404).json({
ERROR: '1',
MESSAGE: 'Unable to update'
});
} else {
res.status(200).json({
SUCCESS: '1',
MESSAGE: 'Record Updated Successfully'
});
}
}
} catch (error) {
console.log(error, 'error');
}
}
};
As everything is dynamic so I am fetching hkey which are the name in MongoDB from a collection and fetching other collection based on req.body received and byteArray is also received from req.body and conversion of it into a string I have to update document as shown in the code
Probleum solved! In my case solution was simple I just changed the code accordingly
hkeyArray.forEach((element) => {
//In this part I was supposed to convert it in string and save with extension
var imagedata = req.files[element].data;
let buff = new Buffer.from(imagedata , 'base64');
fs.writeFile(`element+'.jpg'`,buff, function (err) {
if (err) throw err;
console.log('Saved!');
});
body[element] = element+'.jpg'
});

Running concurrent firebase requests using promises

I am trying to execute number of Firebase Cloud Storage queries concurrently using Promise.all() :
exports.onNewOrder =
functions.database.ref('/orders/{orderId}').onCreate(event => {
const data = event.val();
const orderedBy = data.ordered_by;
const creations = data.creations;
var promises = []
for (var entry of creations.entries()) {
var key = entry[0], value = entry[1];
var creationPromise = admin.database().ref("creations/"+ key);
creationPromise.once("value")
.then((crSnapshot)=>{
const crName = crSnapshot.val().name;
const created = crSnapshot.val().created;
return "crName+'\n'+created”;
}).catch((error) => {
console.log("creation details error : "+ error);
});
promises.push(creationPromise);
}
const userPromise = admin.database().ref("users/"+ orderedBy);
userPromise.once("value")
.then((snapshot) => {
var name = snapshot.val().name;
var email = snapshot.val().email;
const userDetails = name+'\n'+email;
return userDetails;
}).catch((error) => {
console.log("user details error :"+ error);
});
promises.push(userPromise);
return Promise.all(promises).then(results => {
console.log("results: " + results)
return true;
}).catch((error) => {
console.log("sendMail failed : "+ error);
});
});
I am expecting to see complete query result for every executed promise, however I am getting the following instead:
results:
https://....firebaseio.com/creations/cr_1bfd5f16212f44c792a2cbe3e9c6e4cc
https://....firebaseio.com/creations/cr_343c363c2a214487ba8cb7101653f6af
https://....firebaseio.com/creations/cr_e3f7a0bafd1542ac898d96e86155b94e
https://....firebaseio.com/users/us_5e4c28e18e63454c861caa5c4fe6a6f0
Am I missing something?
The following should do the trick.
As detailed here:
The Promise.all(iterable) method returns a single Promise that
resolves when all of the promises in the iterable argument have
resolved or when the iterable argument contains no promises. It
rejects with the reason of the first promise that rejects.
Therefore you need to add some promises to the promises array.
The once() method does return a promise, as explained here in the doc.
exports.onNewOrder =
functions.database.ref('/orders/{orderId}').onCreate(event => {
const data = event.val();
const orderedBy = data.ordered_by;
const creations = data.creations;
const promises = []
for (var entry of creations.entries()) {
var key = entry[0];
var creationPromise = admin.database().ref("creations/"+ key).once("value");
promises.push(creationPromise);
}
const userPromise = admin.database().ref("users/"+ orderedBy).once("value");
promises.push(userPromise);
return Promise.all(promises)
.then(results => {
console.log("results: " + results);
return true;
//actually here you will probably do something else than just write the results to the console.
//In this case you should return a promise (instead of true).
}).catch((error) => {
console.log("sendMail failed : "+ error);
return false;
});
});

Resources