Express returns empty array - node.js

I currently have the following code
router.get('/uri', (request,response) => {
let final = [];
TP.find({userID: request.userID})
.then(tests =>{
tests.forEach(test => {
A.findById(test.assignmentID)
.then(assignment => {
final.push({
testID: test._id,
name: assignment.title,
successRate: `${test.passedTests}/${test.totalTests}`
})
})
.catch(error => {
console.log(error)
})
})
return response.send(final);
})
.catch(err => {
console.log(err);
return response.sendStatus(500);
})
})
The code is supposed to query 2 MongoDB databases and construct an array of objects with specific information which will be sent to the client.
However, I always get an empty array when I call that endpoint.
I have tried making the functions async and make them wait for results of the nested functions but without success - still an empty array.
Any suggestions are appreciated!

forEach doesn't care about promises inside it. Either use for..of loop or change it to promise.all. The above code can be simplified as
router.get('/uri', async (request,response) => {
const tests = await TP.find({userID: request.userID});
const final = await Promise.all(tests.map(async test => {
const assignment = await A.findById(test.assignmentID);
return {
testID: test._id,
name: assignment.title,
successRate: `${test.passedTests}/${test.totalTests}`
};
}));
return response.send(final);
});
Hope this helps.

Related

Why does Async firebase fetching is not working? (NODE JS)

Building a NodeJS REST API.
Trying to send load data from FireBase collection, then sending it to the user (as API response).
Looks like the problem is that it's not waits for the firebase fetch to resolve, but send back a response without the collection data. (tried to use ASYNC-AWAIT but its not working)
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {
snapshot.docs.forEach(msg => {
console.log(msg.data().messageContent)
return {
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}
})
})
}
try {
const chatData = await getChatData()
console.log(chatData)
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData
})
} catch (err) {
if (!err.statusCode) {
err.statusCode(500)
}
next(err)
}
}
As you can see, I've used 2 console.logs to realize what the problem, Terminal logs looks like:
[] (from console.logs(chatData))
All messages (from console.log(msg.data().messageContent))
Is there any way to block the code unti the firebase data realy fetched?
If I correctly understand, you want to send back an array of all the documents present in the messages subcollection. The following should do the trick.
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId;
const collectionRef = db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc');
try {
const chatsQuerySnapshot = await collectionRef.get();
const chatData = [];
chatsQuerySnapshot.forEach((msg) => {
console.log(msg.data().messageContent);
chatData.push({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
});
});
console.log(chatData);
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData,
});
} catch (err) {
if (!err.statusCode) {
err.statusCode(500);
}
next(err);
}
};
The asynchronous get() method returns a QuerySnapshot on which you can call forEach() for enumerating all of the documents in the QuerySnapshot.
You can only await a Promise. Currently, getChatData() does not return a Promise, so awaiting it is pointless. You are trying to await a fixed value, so it resolves immediately and jumps to the next line. console.log(chatData) happens. Then, later, your (snapshot) => callback happens, but too late.
const getChatData = () => new Promise(resolve => { // Return a Promise, so it can be awaited
db.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot(resolve) // Equivalent to .onSnapshot((snapshot) => resolve(snapshot))
})
const snapshot = await getChatData();
console.log(snapshot)
// Put your transform logic out of the function that calls the DB. A function should only do one thing if possible : call or transform, not both.
const chatData = snapshot.map(msg => ({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}));
res.status(200).json({
message: 'Chat Has Found',
chatData
})
Right now, getChatData is this (short version):
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {}) // some things inside
}
What that means is that the getChatData function calls some db query, and then returns void (nothing). I bet you'd want to return the db call (hopefully it's a Promise), so that your await does some work for you. Something along the lines of:
const getChatData = async () =>
db
.collection('chats')
// ...
Which is the same as const getChatData = async() => { return db... }
Update: Now that I've reviewed the docs once again, I see that you use onSnapshot, which is meant for updates and can fire multiple times. The first call actually makes a request, but then continues to listen on those updates. Since that seems like a regular request-response, and you want it to happen only once - use .get() docs instead of .onSnapshot(). Otherwise those listeners would stay there and cause troubles. .get() returns a Promise, so the sample fix that I've mentioned above would work perfectly and you don't need to change other pieces of the code.

Looping through a fetch operation in Node JS

I'm trying to write a collection of Json objects into an array whilst looping through a fetch operation in Node JS. I'm sure the issue is something to do with it being an Async operation, but can't figure out how to get round it.
This is my code:
for (const car of carFilter) {
const carJson = fetch(modelUrl + car, settings)
.then(res => res.json())
.then((json.data.trim));
carData.push(carJson);
}
console.log(carData);
All I'm getting from the console.log is:
Promise { <pending> },
Promise { <pending> },... etc
Which I presume means I'm trying to do the console.log before the data has been push into the array. I may be wrong.
Thanks in advance.
You can do something like this:
const promises = [];
for (const car of carFilter) {
const carJson = fetch(modelUrl + car, settings)
promises.push(carJson);
}
Promise.all(promises)
.then((values) => {
console.log(values); // will be in same order they are called
console.log(values[0]); // will be an Array
})
.catch( e => console.log(e));
So, When we call out an async operation, it returns a Promise(in this case). We are pushing all the promises in an array and can use "Promises.all" that can help to wait for all of them to resolve and gives results.
Note: if any of your promise will be rejected, you won't be able to get the subsequent promises resolved or rejected.
Sample one is here:
const promises = [];
for (let i = 0; i < 10; i++) {
const carJson = promiseCall(); //any promise call
promises.push(carJson);
}
Promise.all(promises)
.then((values) => {
console.log(values); // will be in same order they are called
})
.catch( e => console.log(e));
function promiseCall () {
return new Promise((res, rej) => {
setTimeout(()=> {
res(true);
} ,1000);
})
}

Regarding nested Promises in NodeJS

There are many threads on this and using that i am trying to find solution for below problem.
getMethod() {
execDBQuery(sqlQuery1)
.then(productionQueryRows => {
prodResultFunc(resultSet) // Calling a function to get another set of DB values
.then (result) {
console.log(result) // Final result which will be sent to user
}
});
}
async function prodResultFunc(prodRowSet) {
const results = await Promise.all(
prodRowSet.map( async (objBatchRow) => {
execDBQuery(sqlQuery2)
.then(resultSet => {
read value-1
})
execDBQuery(sqlQuery3)
.then(resultSet => {
read value-2
})
// Create a object using Value-1 & Value-2 and return object to map
});
);
}
I tried implementing below (with one just SQL execution) and it works fine.
async function prodResultFunc(prodRowSet) {
const results = await Promise.all(
prodRowSet.map( async (objBatchRow) => {
execDBQuery(sqlQuery2)
.then(resultSet => {
read values using resultSet
Create object with resultSet values
})
return object
});
);
}
But i want use values for both SQLs (2 & 3) for creating object and this where i am struggling to find syntax for implementation. Any help/pointers to existing threads will be a great help.
Whenever you're dealing with complex Promise chains, I find that the async/await syntax is much easier to read. You end up with a lot less nested code.
The example below shows how you might combine the results of two different queries:
const prodRowSet = [1,2,3];
// Mock query function.
async function execDBQuery(sql) {
return [{ id: 1, query: sql }];
}
async function prodResultFunc(prodRowSet) {
const results = await Promise.all(
prodRowSet.map( async (objBatchRow) => {
let resultSet2 = await execDBQuery('sqlQuery2');
let resultSet3 = await execDBQuery('sqlQuery3');
// Do whatever you want to combine the result sets.
let combinedObj = { resultSet2, resultSet3}
return combinedObj;
})
);
return results;
}
(async() => {
let output = await prodResultFunc(prodRowSet);
console.log("Output:", output);
})();
You might be looking for Promise.All
async function prodResultFunc(prodRowSet) {
const results = await Promise.all(
prodRowSet.map( async (objBatchRow) => {
Promise.all([execDBQuery(sqlQuery2), execDBQuery(sqlQuery3)]).then(function(values) {
//TODO:: Impement you business logic here
console.log(values);
});
});
);
}

Node.js Sequalize creating new rows in forEach loop

I'm trying to create new rows in the database using Sequalize ORM. I receive an array of collections from req.query.collections. For each of those collections I need to create a new userCollection. If none userCollections were created, I wanna respond with internal server error (line 41), otherwise return an array of objects with newly created userCollections.
The problem is, I keep getting an internal server error when I make test requests from Postman. When I check my database, I see that those userCollections were created, so no error occurred.
I know why this happens: because userCollection.build({ stuff }).save() returns a promise. So when I try to console.log userCollections from within .then() statement, I get an array with a newly created collections, just like I should. But by that time server has already responded with internal server error.
Here's my function code:
exports.addCollections = async (req, res, next) => {
const libraryId = req.params.libraryId;
const collections = req.query.collections;
if (!collections)
next(Boom.forbidden());
const userCollections = [];
collections.forEach(async (collectionId, index) => {
const collection = await Collection.findByPk(collectionId);
if (!collection)
return next(Boom.notFound());
userCollection.build({
user_id: req.user.id,
library_id: libraryId,
public_collection_id: collection.id,
title: collection.title,
description: collection.description
})
.save()
.then(newUserCollection => {
userCollections.push(newUserCollection.get({ plain: true }));
// should be printed first, but comes second
// prints out the array with newly created record
console.log(userCollections);
})
.catch(error => {
console.log(error);
});
});
// should be printed second, but comes first
// prints out empty array
console.log(userCollections);
if (userCollections.length === 0) {
next(Boom.internal());
}
res.json(userCollections);
}
Posting the solution
Thanks to Sebastien Chopin who created this tutorial:
https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404
So I added this function:
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
And instead of collections.forEach...(blah blah) (line 10 of the code posted in the question) I do:
try {
await asyncForEach(collections, async (collectionId, index) => {
const collection = await Collection.findByPk(collectionId);
if (!collection)
return next(Boom.notFound());
userCollection.build({
user_id: req.user.id,
library_id: libraryId,
public_collection_id: collection.id,
title: collection.title,
description: collection.description
})
.save()
.then(newUserCollection => {
userCollections.push(newUserCollection.get({ plain: true }));
console.log(userCollections);
})
.catch(error => {
console.log(error);
});
})
} catch (err) {
return next(Boom.internal(err.message));
}

How to send multiple objects from node backend to .hbs

I'm currently trying to send 2 objects to the front .hbs front end. However I cant seem to work out how to do this because I'm using promises.
Currently, my thinking is i perform the sql query, the country and organisation name is extracted, and then each sent to a geocoding api, returned and then squashed together in the same promises. But i'm not sure how to extract these for the render function.
Node
//route for homepage
app.get('/', (req, res) => {
let sql = "SELECT org_name, country_name from places;
let query = conn.query(sql, (err, results) => {
if (err) throw err;
const geoPromise = param => new Promise((resolve, reject) => {
geo.geocode('mapbox.places', param, function(err, geoData) {
if (err) return reject(err);
if (geoData) {
resolve(geoData.features[0])
} else {
reject('No result found');
}
});
});
const promises = results.map(result =>
Promise.all([
geoPromise(result.country_name),
geoPromise(result.org_name)
]));
Promise.all(promises).then((geoLoc, geoBus) => {
res.render('layouts/layout', {
results: JSON.stringify(geoLoc),
businesses: JSON.stringify(geoBus)
});
});
});
});
Front end call
results1 = {{{results}}}
console.log(results1.length)
business1 = {{{businesses}}}
console.log(business1.length)
Wrap your geo.geocode into a Promise
const geoPromise = param => new Promise((resolve, reject) => {
geo.geocode('mapbox.places', param, function(err, geoData) {
if (err) return reject(err);
if (geoData) {
resolve(geoData.features[0])
} else {
reject('No result found');
}
});
});
Combine both calls to geo.geocode
const promises = results.map(result =>
Promise.all([
geoPromise(result.country_name),
geoPromise(result.org_name)
]));
Call them
Promise.all(promises).then(([geoLoc, geoBus]) => {
res.render('layouts/layout', {
results: JSON.stringify(geoLoc),
businesses: JSON.stringify(geoBus)
});
});
As MadWard's answer mentions, deconstructing the argument of the callback of Promise.all is necessary since everything will be in the first argument. Make sure you check out his post for more details
Something important to recall: you will never have more than one argument in a then() callback.
Now you may ask: in the case of Promise.all(), what is this value?
Well, it is an array with all the values from the promises it awaits, in the order in which they are called.
If you do:
Promise.all([
resolveVariable1, resolveVariable2, resolveVariable3
]).then((values) => {
})
values will be [variable1, variable2, variable3], the three variables that the promises resolve to.
Your case is, however, a bit more complicated. What is gonna be returned at the end is a 2-D array containing every entry. It is an array of length results.length, and each of its element has a length of 2. The first element is the result, and the second one is the business.
Here is your snippet:
Promise.all(promises)
.then((values) => {
let results = values.map(elmt => elmt[0]);
let businesses = values.map(elmt => elmt[1]);
res.render('layouts/layout', {
results: JSON.stringify(results),
businesses: JSON.stringify(businesses)
});
})

Resources