How to respond with array of query results in Node/ Express/ Postgresql? - node.js

newbie here. I am trying to fetch an array of objects from my local database through two select queries, using knex.js and postgresql like so:
app.get('/trips/:name', (req, res) => {
const {name} = req.params;
db.select('tripid').from('member')
.where('name', '=', name)
.then(data => {
const array = data.map(elem => {
db.select('tripname')
.from('trip')
.where('id', '=', elem.tripid)
.then(obj => {
array.push(obj[0])
})
})
return array
})
.then(arr => res.json(arr))
.catch(err => res.status(400).json(err))
})
I want to push all the objects that the second query returns into an array and respond to the front end. However, it always returns an empty array, which I suspect because
.map
is asynchronous, so the array is returned before the loop finishes. How can I fix this? Any help is appreciated. Thanks. Sorry if my code is hard to read.

try this way using async await :
app.get('/trips/:name', async (req, res) => {
try{
const {name} = req.params;
let arr = await db.select('tripid').from('member').where('name', '=', name);
if(arr && arr.length>0){
for(let item of arr){
let tripResult = await db.select('tripname').from('trip').where('id', '=', item.tripid).catch(e=>e);
item.trip = tripResult && tripResult[0]? tripResult[0]:null;
}
}
return res.json(arr);
}catch(error){
return res.status(400).json(error);
}
}
2nd way :
app.get('/trips/:name',(req, res) => {
const {name} = req.params;
db.select('tripid').from('member').where('name', '=', name)
.then(arr =>{
Promise.all(arr.map(item =>{
db.select('tripname')
.from('trip')
.where('id', '=', item.tripid)
.then((tripResult=>{
item.trip = tripResult && tripResult[0]? tripResult[0]:null;
return item;
}).catch(e=>e);
})
).then(arr => {
return res.status(200).json(arr);
})
}).catch(err => res.status(400).json(err));

Related

Promise.all and .map returning null

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

Express router not awaiting the forEach loop

Express router is not awaiting my forEach loop and sends the old unmanipulated object as a response instead of the new manipulated data.
Here I am using Sequalize as my ORM.
router.get('/', async (req,res) => {
try {
let trainings = await db.Training.findAll();
let locations = await db.Location.findAll();
await locations.forEach(location => {
trainings.forEach(training => {
if(location.trainingId == training.id){
training["location"] = location
}
})
})
res.status(200).json({
training:trainings
})
} catch(err) {
console.log(err);
res.status(404).json({
message : err
})
}
})
Basically you are using the await keyword against a synchronous process which is
locations.forEach(location => {
trainings.forEach(training => {
if(location.trainingId == training.id){
training["location"] = location
}
})
})
These lines of code doesn't return any promise or behave like a promise. So one solution can be having a function
function modify(trainings,locations){
return new Promise((resolve,reject)=>{
locations.forEach(location => {
trainings.forEach(training => {
if(location.trainingId == training.id){
training["location"] = location
}
})
})
resolve('done')
})
}
then have it like this
let trainings = await db.Training.findAll();
let locations = await db.Location.findAll();
await modify(trainings,locations)
or you can simply remove the await keyword from your current state of code.

Make multiple firestore queries in a cloud function

I want to create a scheduled cloud function that is generating employees bonus at the end of each month.
To do that, i need a list of all employees, of all invoices of that user and of all existing bonus, all contained in firestore collections.
So i need 3 firestore collections but can't find any solution on how to do query that in a cloud function.
i tried this for now :
exports.generateBonus = functions.https.onRequest(async (req, res) => {
var listEmployee = [];
var listInvoice = [];
const employeeRef = admin.firestore().collection('employee');
const invoiceRef = admin.firestore().collection('invoice');
const promiseFacture = new Promise((resolve,reject)=>{
return factureRef.get();
})
.then(list_invoice => {
listInvoice = list_invoice.docs.map(doc => {
return doc.data();
});
})
.catch(error => {
console.log("got an error",error);
});
const promiseEmployee = new Promise((resolve,reject)=>{
return employeeRef.get();
})
.then(list_employee => {
listEmployee = list_user.docs.map(doc => {
return doc.data();
});
})
.catch(error => {
console.log("got an error",error);
});
Promise.all([promiseInvoice, promiseEmployee])
.then((values) => {
console.log(values);
return res.send('ok');
})
.catch(error => {
console.log(error);
})
});
But it return me two empty arrays in 1 sec
Does anyone know how to do this ? Thank you
The following, using destructuring assignment syntax, should do the trick:
exports.generateBonus = functions.https.onRequest(async (req, res) => {
const employeesRef = admin.firestore().collection('employee');
const invoicesRef = admin.firestore().collection('invoice');
const [employeesSnapshot, invoicesSnapshot] = await Promise.all([employeesRef.get(), invoicesRef.get()]);
const listEmployees = employeesSnapshot.docs;
const listInvoices = invoicesSnapshot.docs;
//Logging
listEmployees.forEach(snap => {
console.log(snap.data());
});
listInvoices.forEach(snap => {
console.log(snap.data());
});
//...
res.status(200).send(...); //Adapt the ... to a meaningful value
});
Note that the get() method returns a Promise, so you don't need to wrap it in another Promise.
(note that I have added an s to all the collections/snapshots variables names).

Firebase firestore get data from collections documents and then subcollection documents in one array

I am using node server with express and trying to get data from the firestore database. My database structure is as follows
-Resource Collection
- Documents with fields
- comments subcollection
- documents with fields
- subresource subcollection
- documents with fields
I am trying to map all the above data in a single array with subresource and comments being nested array inside the single array. Following is my code. The log has the correct data in one array but when I am sending the response back it only shows 1 resource and that too is random everytime. Shouldnt promise.all then block execute after all the data has been collected? Am i even using the promises correctly?
response.set('Cache-Control','public, max-age=300, s-maxage=600');
db.collection('Resources').get()
.then(snapshot => {
let Resources = snapshot.docs.map(doc => {
var promises = [];
let documentData = doc.data();
documentData['id'] = doc.id;
promises.push(db.collection('Resources').doc(documentData.id).collection('SubResources').get()
.then(snapshot => {
documentData['subresources'] = snapshot.docs.map(doc => {
let subResourceData = doc.data();
subResourceData['id'] = doc.id;
return subResourceData;
})
}));
promises.push(db.collection('Resources').doc(documentData.id).collection('Comments').get()
.then(snapshot => {
documentData['comments'] = snapshot.docs.map(doc => {
return doc.data();
})
}));
Promise.all(promises).then(function(){
console.log(documentData);
return response.json(documentData);
})
})
})
.catch(err => {
console.log('Error getting documents', err);
});
});
Your Promise.all() catches the promises of fetching subresource and comments only, and not the "Resource"s themselves. so for whichever Resource it can complete fetching the subresource and comments first, it resolves and returns the data.
Below is a slight modification to your code so that it waits for all the Resources to complete pulling comments/subresources and then returns them as an array.
response.set('Cache-Control','public, max-age=300, s-maxage=600');
db.collection('Resources').get()
.then(snapshot => {
let ResourcePromises = snapshot.docs.map(doc => {
var promises = [];
let documentData = doc.data();
documentData['id'] = doc.id;
promises.push(db.collection('Resources').doc(documentData.id).collection('SubResources').get()
.then(snapshot => {
documentData['subresources'] = snapshot.docs.map(doc => {
let subResourceData = doc.data();
subResourceData['id'] = doc.id;
return subResourceData;
})
}));
promises.push(db.collection('Resources').doc(documentData.id).collection('Comments').get()
.then(snapshot => {
documentData['comments'] = snapshot.docs.map(doc => {
return doc.data();
})
}));
return Promise.all(promises).then(function(){
//console.log(documentData);
//return response.json(documentData);
return documentData;
})
});
Promise.all(ResourcePromises).then(function(allResources) {
return response.json(allResources);
})
})
.catch(err => {
console.log('Error getting documents', err);
});
You may want to refactor the code to separate functions so that they're more readable.

Nodejs wait for query

I'm using Nodejs with MongoDB(mongoose along with express).
Since I don't trust the user data, I need to verify it from the database.
input data:
{
"id": "someid",
"nottrusteddata": [ {"id": "1"}, {"id" :"2"}]
}
In my function, I'm verifying the data:
router.post("/validate", (req, res,next) =>{
let validated_data = validate_data(req);
console.log(JSON.stringify(validated_data));
const mydata = new Mydata({
id: req.body.id,
lst : validated_data
});
console.log("mydata: " + JSON.stringify(mydata));
/* Some Usefull stuff is here */
res.status(200).json();
}
function validate_data(req){
let validated_data = []
for(let i = 0; i < req.body.nottrusteddata.length; i++)
{
Databaseobject.findOne({'id': req.body.nottrusteddata[i].id})
.exec()
.then(dbobject =>{
if(dbobject) // not undefined, it exists in the database
{
// Some logic with the object returned from the database
let tmp_object = {};
tmpobject.id = dbobject.id;
// Append it to the list, so that the upper function can use it
validated_data.push(tmp_object);
}
})
}
return validated_data;
}
The desired output should contain the correct information coming from the database, however, due to the async nature of the nodejs, validated_data returns null.
I have also tried using Promise. I couldn't succeed it.
const validate_data = function(req){
return new Promise(function(resolve,reject){
let validated_data = []
for(let i = 0; i < req.body.nottrusteddata.length; i++)
{
Databaseobject.findOne({'id': req.body.nottrusteddata[i].id})
.exec()
.then(dbobject =>{
if(dbobject) // not undefined, it exists in the database
{
let tmp_object = {};
tmpobject.id = dbobject.id;
validated_data.push(tmp_object);
}
})
}
resolve(validated_data);
}
}
What am I doing wrong? How can I wait for the database query to finish, then execute the main part? If there is only one validation, I could've used .then(). However, the list might have contained many elements and I need to wait for all of them to be verified.
Your Databaseobject.findOne() calls are asynchronous so your promise will resolve before any of them complete.
You can make use of Promise.all to wait until all of your promises resolve.
Hopefully, this will work for you:
router.post("/validate", (req, res) => {
validate_data(req.body.nottrusteddata)
.then(validated_data => {
const mydata = new Mydata({
id: req.body.id,
lst: validated_data
})
// Some useful stuff is here
res.status(200).json()
})
.catch(err => {
// Handle error
})
}
function validate_data(nottrusteddata) {
// Create array of pending promises
const promises = nottrusteddata
.map(item => {
return Databaseobject
.findOne({ 'id': item.id })
.exec()
})
// Wait for all promises to resolve
return Promise.all(promises)
.then(docs => {
return docs
.filter(dbobject => dbobject) // Filter out undefined
.map(dbobject => {
return { id: dbobject.id }
})
})
}
If you want, you could also use async-await here:
router.post("/validate", async (req, res) => {
try {
const validated_data = await validate_data(req.body.nottrusteddata)
const mydata = new Mydata({
id: req.body.id,
lst: validated_data
})
// Some useful stuff is here
res.status(200).json()
}
catch(err) {
// Handle error
}
})

Resources