Promise.all and .map returning null - node.js

const returnPostData = async (req, res, initialPostsQueryArray) => {
const promises = initialPostsQueryArray.map((post) => {
let postObject;
voteCount = sumValues(post.voting_options);
pool.query('SELECT voting_option FROM votes WHERE user_id = $1 AND post_id = $2',
[req.user.userId, post.post_id],
(error, results) => {
if (error) {
console.log(error);
return res.json({'Error': error.detail});
}
userVoteOption = results.rows[0].voting_option;
});
postObject.voteCount = voteCount;
postObject.userVoteOption = userVoteOption;
return postObject;
});
return Promise.all(promises).then(postData => {
return res.json(postData);
})
}
I'm trying to return an array of the postObjects for each post. For some reason it keeps on printing null for these objects because the return res.json is somehow running before the promises are even done. Any help is appreciated.
I had this problem before and used the same code, but it didn't work for this one for some reason.

Multiple problems:
You return postObject before userVoteOption is assigned
You don't actually use promises with node-postgres
…and therefore the return value of the map callback is not a promise
You never initialse postObject
You marked your function as async but never use await
You handle each error in the loop individually (which leads to res.json being called multiple times in case of multiple errors)
To fix these, use
async function returnPostData(req, res, initialPostsQueryArray) {
try {
const promises = initialPostsQueryArray.map(async (post) => {
const voteCount = sumValues(post.voting_options);
const results = await pool.query(
'SELECT voting_option FROM votes WHERE user_id = $1 AND post_id = $2',
[req.user.userId, post.post_id]
);
const userVoteOption = results.rows[0].voting_option;
const postObject = { voteCount, userVoteOption };
return postObject;
});
const postData = await Promise.all(promises);
res.json(postData);
} catch(error) {
console.log(error);
res.json({'Error': error.detail});
}
}
In addition, you actually shouldn't use a loop at all here. Just query multiple rows from postgres at once! Using this approach to supply the ids:
async function returnPostData(req, res, initialPostsQueryArray) {
try {
const voteCounts = new Map();
const ids = [];
for (const post of initialPostsQueryArray) {
ids.push(post.post_id);
voteCounts.set(post.post_id, sumValues(post.voting_options));
}
const {rows} = await pool.query(
'SELECT post_id, voting_option FROM votes WHERE user_id = $1 AND post_id = ANY($2::int[])',
[req.user.userId, ids]
);
const postData = rows.map(row => {
const postObject = {
voteCount: voteCounts.get(row.post_id),
userVoteOption: row.voting_option,
};
return postObject;
});
const postData = await Promise.all(promises);
res.json(postData);
} catch(error) {
console.log(error);
res.json({'Error': error.detail});
}
}

Related

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

Problem to use a Map in Firebase Functions

I am trying to get the length of a Map and I keep getting "undefined". Could please someone tell me what am I doing wrong?
This is the part of the code that gives me problems.
const GYMdetail: { [key: string]: number} = {};
GYMdetail[`${doc.data().name} (${doc.data().personalID})`] = 650;
const subtotal = 650 * GYMdetail.size;
This is the complete function code
export const addGymMonthlyExpense =
functions.https.onRequest((request, response) => {
const query1 = admin.firestore().collection("users");
const query = query1.where("subscriptions.gym.active", "==", true);
query.get()
.then(async (allUsers) => {
allUsers.docs.forEach(async (doc) => {
if (doc != undefined) {
const houseForGym = doc.data().subscriptions.gym.house;
await admin.firestore()
.doc(`houses/${houseForGym}/expenses/2022-04`)
.get().then((snapshot) => {
if (snapshot.data() == undefined) {
console.log(`${houseForGym}-${doc.data().name}: CREAR!!`);
} else if (snapshot.data()!.issued == false) {
let detail: { [key: string]: any} = {};
const GYMdetail: { [key: string]: number} = {};
detail = snapshot.data()!.detail;
GYMdetail[
`${doc.data().name} (${doc.data().personalID})`
] = 650;
const subtotal = 650 * GYMdetail.size;
detail["GYM"] = {"total": subtotal, "detail": GYMdetail};
snapshot.ref.set({"detail": detail}, {merge: true});
}
return null;
})
.catch((error) => {
console.log(
`${houseForGym} - ${doc.data().name}: ${error}`);
response.status(500).send(error);
return null;
});
}
});
response.send("i");
})
.catch((error) => {
console.log(error);
response.status(500).send(error);
});
});
Since you are executing an asynchronous call to the database in your code, you need to return a promise from the top-level code; otherwise Cloud Functions may kill the container when the final } executes and by that time the database load won't be done yet.
So:
export const addGymMonthlyExpense =
functions.https.onRequest((request, response) => {
const query1 = admin.firestore().collection("users");
const query = query1.where("subscriptions.gym.active", "==", true);
return query.get()
...
Next you'll need to ensure that all the nested get() calls also get a chance to finish before the Functions container gets terminated. For that I recommend not using await for each nested get call, but a single Promise.all for all of them:
query.get()
.then(async (allUsers) => {
const promises = [];
allUsers.docs.forEach((doc) => {
const houseForGym = doc.data().subscriptions.gym.house;
promises.push(admin.firestore()
.doc(`houses/${houseForGym}/expenses/2022-04`)
.get().then((snapshot) => {
...
});
});
response.send("i");
return Promise.all(promises);
})
.catch((error) => {
console.log(error);
response.status(500).send(error);
});

Express route still returning item deleted from mongodb

I have this route to delete a "garage" from the mongodb database and then grab some of the remaining garages. For some reason it is still returning the deleted garage and returning it, but if I check the database the delete was successful.
router.post('/garage/delete', requireLogin, async (req, res) => {
let limit = 20;
try {
let list = req.body;
list.map( async (item) => {
const existingGarage = await Garage.find({_id: item._id});
if (existingGarage) {
await Garage.deleteOne({_id: item._id});
} else {
res.status(400).send("Garage not found");
}
})
const allGarages = await Garage.find().limit( limit );
console.log(allGarages);
res.send(allGarages);
} catch {
res.status(400).send("Garage not found");
}
})
You will need to await all the promises returned by the map function.
Promise.all awaits an array of promises and runs them in parallel.
since you're passing an async function to the map function you will need to await all the promises returned by that async function
Other solution is to use a for of loop
router.post('/garage/delete', requireLogin, async (req, res) => {
let limit = 20;
try {
let list = req.body;
await Promise.all(list.map( async (item) => {
const existingGarage = await Garage.find({_id: item._id});
if (existingGarage) {
await Garage.deleteOne({_id: item._id});
} else {
res.status(400).send("Garage not found");
}
}))
const allGarages = await Garage.find().limit( limit );
console.log(allGarages);
res.send(allGarages);
} catch {
res.status(400).send("Garage not found");
}
})

Formatting MongoDb response for API

I'm trying to retrieve a collection in MongoDb and the problem is the API already responds even though the processing is not yet done. So basically what I'm trying to do is retrieve the results using find, iterate through the results using foreach, and push each row to an array.
I've tried a variety of suggestions but none work so far. Below is a rough idea of what I'm trying to pull off.
get: async function (req, res, next) {
let messagesArray = []
let sessionId = req.query.session
client.connect(err => {
try{
const collection = client.db("xxxxxxx").collection("xxxxxxx")
let results = collection.find({},{sessionId:sessionId})
for (const result of results){
order = {"text" : order.partner+", "+order.order+ ", "+order.quantity}
messagesArray.push(order)
}
}
catch(e){
}
client.close()
res.send(200,{messages:messagesArray})
})
},
for loop will execute asynchronous so before your for loop is getting finished res is sent so try for the following
get: async function (req, res, next) {
let messagesArray = []
var counter = 0;
let sessionId = req.query.session
client.connect(err => {
try{
const collection = client.db("xxxxxxx").collection("xxxxxxx")
let results = collection.find({},{sessionId:sessionId})
for (const result of results){
order = {"text" : order.partner+", "+order.order+ ", "+order.quantity}
messagesArray.push(order)
counter++;
if(counter == results.length) {
client.close()
res.send(200,{messages:messagesArray})
}
}
}
catch(e){
}
})
},
use await to wait for result to available :
const MongoClient = require('mongodb').MongoClient;
const url = 'mongodb://localhost:27017';
get: async function (req, res, next) {
let messagesArray = []
let sessionId = req.query.session
const client = await MongoClient.connect(url, { useNewUrlParser: true }).catch(err => { console.log(err); });
if (!client) { return;}
try{
const collection = client.db("xxxxxxx").collection("xxxxxxx")
let results = await collection.find({sessionId:sessionId})
console.log(results);
// ...
}
catch(e){
console.log(e);
}
finally {
client.close();
}
},

Node js, Wait, until get all data from MongoDB

I have a problem with async function and callbacks in Node js. I need to get my friends' all posts and display it. But if i do that without setTimeout(), it returns only some part of data. How can i solve this problem without putting setTimeout? Of course it's absurd to wait for 5-6 or 10 seconds to get all data. I also tried with Promises, but again response is incomplete. Please someone can help me?
//Sending request with axios to Controller
axios.post(packages.proxy+'users/getFriendsPosts',{id: user_id},config)
.then(res => {
// Code for displaying result
})
//User Controller
router.post("/getFriendsPosts", getFriendsPosts);
//Send request body to userService.js
function getFriendsPosts(req, res, next) {
userService.getFriendsPosts(req.body, function(posts, user){
res.json({posts,user});
})
.catch(err => next(err));
}
//userService.js
module.exports = {
getFriendsPosts,
};
async function getFriendsPosts(user,callback){
var arr = [];
var array = [];
MongoClient.connect(url, async function(errr, db) {
if (errr) throw errr;
var dbo = db.db("drone-x");
//Find user
dbo.collection("users").find({_id: ObjectId(user.id)}).toArray(async function(err, result) {
if (err) throw err;
result.forEach(async function(element, index) {
if(element.friends.length != 0){
element.friends.forEach(async function(elem) {
//Find user's friends
dbo.collection("users").find({_id: ObjectId(elem.id)}).toArray(async function(error, res) {
if (error) throw error;
//push user's friends to arr
arr.push(res);
res.forEach(async function(elements) {
//Find user's friends posts
dbo.collection("posts").find({userId: elements._id.toString()}).toArray(async function(errors, results) {
if (errors) throw errors;
//push user's friends posts to array
array.push(results);
//callback results through setTimeout
setTimeout(async function(){ await callback(array, arr); db.close(); }, 2000);
});
});
});
});
}
else
{
await callback("0");
}
});
});
});
}
If i don't use setTimeout function, it just returns 2-3 data, but with setTimeout, it returns all data. And if data will be raise, then i need to increase the setTimeout time. But of course it's not good idea. Someone can help me?
You should use try catch in this code
getFriendsPosts = async (user,callback) => {
const arr = [];
const array = [];
const db = await MongoClient.connect(url);
const dbo = db.db("drone-x");
const results = await dbo.collection("users").find({_id: ObjectId(user.id)})
const resultPromise = _.map(results, async element => {
const friends = _.get(element, 'friends', [])
if(friends.length != 0) {
const friendPromise = _.map(friends, async friend => {
const ress = await dbo.collection("users").find({_id: ObjectId(friend.id)})
arr.push(ress);
const resPromise = _.map(ress, async res => {
const posts = await dbo.collection("posts").find({userId: res._id.toString()})
const postPromise = _.map(posts, post => {
array.push(post);
})
await Promise.all(postPromise)
})
await Promise.all(resPromise)
})
await Promise.all(friendPromise)
}
})
await Promise.all(resultPromise)
return { arr , array }
}
I am not recommand this way it take too much time. You should use mongoose and use aggregation for long Query.

Resources