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));
}
Related
I am working on a project and on the newer side of Node. This is someone else's original implementation and I'm having an issue with promises and I know it most likely has to do with async/await or promise.all but I've spent alot time trying and still not getting the desired result.
I am going to the PlaceStore and returning a promise to get a list of people from a sql database.
After I have the list of people. I map over the the list so I can have each person's id. Inside the map (which may be the problem because maps are not asynchronous) I am trying to find the most recent thing done by that person by calling the getMostRecentThingByPerson function that goes to the ThingStore to get the most recent thing.
When I log the resent in the result of the getMostRecentThingByPerson it does log the correct values but I don't know how to make the value available in the map function so I can update and return the object containing the 'Most Recent Thing' and export it to Csv.
I've read alot about how this is an important topic and thank you so much for your guidance with this issue.
exports.exportPeople = (req, res, next) => {
const id = req.body.id;
const place = req.place;
PlaceStore.findPerson(place, async (err, people) => {
if (err) {
return new Error(err);
}
const peopleForCsv = people.map((person) => {
const mostRecentThing = this.getMostRecentThingByPerson(person.id) // tried adding await here but does not work because of the map.
return {
'First Name': person.first_name,
'Last Name': person.last_name,
'Most Recent Thing': mostRecentThing
};
});
const json2csvParser = new Parser({ delimiter: ',' });
const csv = json2csvParser.parse(peopleForCsv);
return res
.status(200)
.attachment('people_' + id + '.csv')
.set('Content-Type', 'text/csv')
.send(csv);
});
};
exports.getMostRecentThingByPerson= (id) => {
ThingStore.getThings({"person_id": id}, (err, things) => {
if (err) {
return new Error(err);
}
result = things.rows.length > 0 ? things.rows[0].capture_date : ''
console.log(`Recent thing result for ${id}`, result) //example date
return result
})
return result
}
As mentioned in the comments below your question, you need to promisify your callback-based code. This is a fundamental pattern with which you need to get familiar.
To promisify your function getMostRecentThingByPerson, you need to create and return a promise which resolves on the result or rejects on the error (if any):
exports.getMostRecentThingByPerson = (id) => {
return new Promise((resolve, reject) => {
ThingStore.getThings({"person_id": id}, (err, things) => {
if (err) {
reject(err);
} else {
const result = things.rows.length > 0 ? things.rows[0].capture_date : ''
console.log(`Recent thing result for ${id}`, result) // example date
resolve(result)
}
})
})
}
Now, getMostRecentThingByPerson returns a promise, so you need to await its resolution when calling it. If you do that inside a loop, you need to await the completion of each promise that will be created by each iteration of the loop:
exports.exportPeople = (req, res, next) => {
const id = req.body.id;
const place = req.place;
PlaceStore.findPerson(place, async (err, people) => {
if (err) {
return new Error(err);
}
const peopleForCsv = await Promise.all(people.map(async (person) => {
const mostRecentThing = await this.getMostRecentThingByPerson(person.id)
return {
'First Name': person.first_name,
'Last Name': person.last_name,
'Most Recent Thing': mostRecentThing
}
});
const json2csvParser = new Parser({ delimiter: ',' });
const csv = json2csvParser.parse(peopleForCsv);
return res
.status(200)
.attachment('people_' + id + '.csv')
.set('Content-Type', 'text/csv')
.send(csv);
});
};
Note that your current code for exportPeople does not return anything so it relies on a side-effect from calling PlaceStore.findPerson. I don't know if that works but I think a better practice would be to promisify PlaceStore.findPerson instead, and return its promised value (csv). Then you await it and return res.status(200)/* rest of the code */
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.
I'm retrieving a list of people from the database using getPeople(). As soon as I receive them as res, I want to prepare them to be stored in my local mongodb if they do not exist. Sometimes there're duplicate entries (for one id) within res. My issues is that it's not waiting for Person.create(pers) to finish, continues searching if this id is already in mongodb, can't find any since Person.create(pers) is still creating it and starts the second Person.create(pers)..
this.getPeople()
.then(res => {
return Promise.all(res.map(pers => {
pers.birthday = df(pers.birthday, 'dd.mm.yyyy')
pers.pickedUp = false
console.log(pers.id)
return Person
.find({ id: pers.id })
.exec()
.then(found => {
if (found === undefined || found.length == 0)
return pers
})
.then(pers => {
return Person
.create(pers)
.then(console.log('created'))
.catch(err => console.log(err))
})
}))
}).catch(err => console.log(err))
I expected the console output to be like this:
940191
created
940191
created
Instead, I'm getting this:
940191
940191
created
created
That's because Promise.all simply awaits all the promises you're mapping. It does not guarantee any order in which the promises are resolved.
If you want to sequentially process the elements of your res-array, you can simply use a for .. of loop in combination with async/await (note that this still needs some error handling, but it should give you something to start with):
async function getPeopleAndCreateIfNotExisting() {
const persons = [];
const res = await this.getPeople();
for (const pers of res) {
pers.birthday = df(pers.birthday, 'dd.mm.yyyy');
pers.pickedUp = false;
console.log(pers.id)
const found = await Person
.find({ id: pers.id }).exec();
if (found) {
persons.push(pers);
} else {
persons.push(await Person.create(pers));
}
}
return person;
}
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.
I'm learning to make an API in node js with express library
i have two things : product , order
and each order has an address , one or more product and a total price which is based by price of products
when i try to save an order i just pass the products IDs so in code i should get the products and sum their prices plus i want to show the products in the response so what i did was to put the ids in an array and i executed a foreach loop in array and in every loop i get the product and push it in an array .
the problem is that the response (actually every code after foreach loop) is executed before i get the products,
i have searched about Async/Await and i tried to implement it but it didn't work
my code is like this
function getproductpromis(options){
return new Promise((resolve,reject) =>{
http.get(options, resolt =>{
let data ;
resolt.on('data', d => {
data +=d;
})
resolt.on('end', f =>{
resolve (data);
})
resolt.on('error', e =>{
reject(e);
console.log(e);
})
});
});
}
async function getproduct(element){
let object;
const options = {
hostname:'localhost',
port:5000,
path:'/products/'+element,
mathod:'GET'
}
const request_call = await getproductpromis(options);
console.log('request_call');
console.log(request_call);
return request_call;
}
router.post('/',(req,res,next) => {
let prod;
console.log(req.body.products);
const ids = req.body.products;
let totalprice = 0;
let objects =[];
ids.forEach(element => {
prod = getproduct(element);
console.log('prod');
console.log(prod);
objects.push(prod);
});
res.status(200).json({
objects
});
}
and here is my log:
[ '5dab2706baab4b1e90dc30ff', '5dab2792f0662d4f54bbf1f1' ]
prod
Promise { <pending> }
prod
Promise { <pending> }
POST /orders 200 56.974 ms - 19
{ _id: 5dab2706baab4b1e90dc30ff,
name: 'langero',
quantity: 5,
price: 1500,
__v: 0 }
GET /products/5dab2706baab4b1e90dc30ff 200 208.973 ms - 114
request_call
undefined{"massage":"product found","product":{"ID":"5dab2706baab4b1e90dc30ff","name":"langero","quantity":5,"price":1500}}
{ _id: 5dab2792f0662d4f54bbf1f1,
name: 'langer',
quantity: 5,
price: 1500,
__v: 0 }
GET /products/5dab2792f0662d4f54bbf1f1 200 1172.254 ms - 113
request_call
undefined{"massage":"product found","product":{"ID":"5dab2792f0662d4f54bbf1f1","name":"langer","quantity":5,"price":1500}}
and response i get in postman
response
Change your forEach loop to for...of loop
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of]
Here is your updated router.post using async/await and for...of loop
router.post('/',async (req,res,next) => {
let prod;
console.log(req.body.products);
const ids = req.body.products;
let totalprice = 0;
let objects =[];
for (let element of ids) {
prod = await getproduct(element);
console.log('prod');
console.log(prod);
objects.push(prod);
}
res.status(200).json({
objects
});
}
So you want to execute and an array of Promises, you're on the right track but may I point you in the direction of Async/Await. Here I marked your router.post callback as async so I can use await. I use Array.map to return an array of promises that get passed to Promise.all
router.post("/", async (req, res, next) => {
const ids = req.body.products;
const objects = await Promise.all(
ids.map((id) => {
return getproduct(id);
})
);
res.status(200).json({
objects
});
});