I'm trying to wait for all of my database calls in a loop to complete before I proceed. I add each call, which are promises, to an array and then use Promise.all() to wait for all of them. However, there are nested DB calls like user.increment() or user.create() which Promise.all() does not seem to wait for. The output of this snippet usually goes:
User found, incrementing wins...
Promise.all()
User updated
The Promise.all block is being run before the nested DB calls complete. I feel like I'm missing something really easy.
let dbCalls = [];
for(var i = 0; i < users.length; i++){
let messageUser = users[i];
//try to find user by id
dbCalls.push(db.user.findOne({
where: {
id: messageUser.id
}
}).then(function(user){
if(user){
//found user, increment them
user.increment('wins').then((user) => {
console.log('User found, incrementing wins...');
user.reload();
}).then(user => {
console.log('User updated')
return user;
});
} else{
//user wasn't found, create them
console.log(`${messageUser.username} was not found, creating user...`);
db.user.create({
username: messageUser.username,
id: messageUser.id,
wins: 1
}).then(function(user){
console.log('created user');
return user;
});
}
}).then(res => {
return res;
})
);
}
Promise.all(dbCalls).then(res =>{
console.log('Promise.all()' + res);
});
You forgot to return that promises. So they are not included in your promise chain
Just change lines with db-promises into return user.increment('wins').then((user) => {
Since you are running multiple DB queries that require one to finish before the other one, you should look in NODE.js waterfall calls. It allows promises to be used and you can set which ones require other to finish first before firing.
https://www.npmjs.com/package/promise-waterfall
there is one good example
This library goes as far as even allowing you to wait for a returned value to use in another async call.
You need to chain all of the internal promises to that the item that gets pushed to the array is the full chain. For better readability, consider extracting the increment and create to their own function:
const increment = user => user.increment('wins').then((user) => {
console.log('User found, incrementing wins...');
return user.reload();
}).then(user => {
console.log('User updated')
return user;
});
const create = user => {
//user wasn't found, create them
console.log(`${messageUser.username} was not found, creating user...`);
return db.user.create({
username: messageUser.username,
id: messageUser.id,
wins: 1
}).then(user => {
console.log('created user');
return user;
});
};
const dbCalls = users.map(messageUser => db.user.findOne({
where: {
id: messageUser.id
}
}).then(user => (
user
? increment(user)
: create(user)
)));
Promise.all(dbCalls).then(res =>{
console.log('Promise.all()' + res);
});
Related
My application (React) uses the service of Firestore. When a user changes his username, I must verify that this username is unique. For that, I want to use a Cloud function for more reliability.
However, my function always goes into an infinite loop. I don't see how I can get out of it.
Here is my function.
Any help would be much appreciated.
exports.checkUsername = functions.firestore
.document('/users/{userId}')
.onUpdate((change, context) => {
const before = change.before.data();
const after = change.after.data();
if (after.username === before.username) {
return null;
}
db.collection('users').where('username', '==', after.username).get()
.then((query) => {
if (!query.empty) {
// Username not available
return change.before.ref.update({ username: before.username });
}
// Username available
return null;
})
.catch(() => {
return change.before.ref.update({ username: before.username });
})
});
You're not dealing with promises correctly. Your function needs to return a promise that resolves after all the asynchronous work is complete. That's how Cloud Functions knows it's safe to terminate and clean up. Right now your function returns nothing in the case where the before and after usernames are not equal.
Minimally, you should return the final promise from the chain:
return db.collection('users').where('username', '==', after.username).get()
.then(...)
.catch(...)
I'm making an authenticate function. It actually works with hardcoded users, but when I start getting users from Firebase, things start getting asynchronous and issues with timing start happening.
I've got a kind of long-winded Javascript function here that I believe returns a promise.
function authenticate({ username, password }) {
return users.then((querySnapshot) => {
return querySnapshot.forEach(doc => {
let user = doc.data();
if (user.username.toUpperCase() == username.toUpperCase())
return bcrypt.compare(password, user.password).then(function (result) {
console.log(password);
console.log(user.password);
console.log(result);
if (result) {
const token = jwt.sign({ sub: user.id }, config.secret);
const { password, ...userWithoutPassword } = user;
return {
...userWithoutPassword,
token
};
}
})
})
})
}
Console logging seems to confirm that this is a promise. I'll be honest, I copy-pasted a lot of the code inside, so I'm still not entirely sure how it works, but the promise syntax is at least something I'm confident in. After I go through a list of users pulled from Firebase and check that both username and password match, the guts of if (result) should run. result does come back as true, which is correct for what I'm trying to log in with, but my password form rejects me because it continues processing before the authenticate method is finished.
In another Javascript file, I have the method that calls this one.
function authenticate(req, res, next) {
console.log(req.body);
userService.authenticate(req.body)
.then(user => console.log(user))
//.then(user => user ? res.json(user) : res.status(400).json({ message: 'Username or password is incorrect' }))
.catch(err => next(err));
}
I'm learning a lot about asynchronous programming recently but this is defying my expectations a bit. Surely doing .then() on authenticate() should run authenticate(), get a promise, even if it's unresolved, then wait for it to resolve before executing the rest of the statements? The current issue is that the method goes ahead, finds no value for user, then throws a 400, which I think is an issue with asynchronicity. Can anyone explain why the outer authenticate function isn't waiting and how I could make it do that?
There are two possible issues:
Result of forEach
The forEach function returns undefined, see Array.prototype.forEach(). If you need the result of the iteration, you can use Array.prototype.map()
Waiting for the Promise
The following statement sounds like the code does not await the result properly:
my password form rejects me because it continues processing before the authenticate method is finished.
If you have a javascript promise, you can use the await keyword in order to continue the function execution only if the promise is either resolved or rejected. Have a look at the examples here: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/await
In your example, the authenticate function would look like this:
async function authenticate(req, res, next) {
console.log(req.body);
await userService.authenticate(req.body)
.then(...)
.catch(...);
}
Note the async and await keywords. That way it only returns after the userService.authenticate(..) call is fully processed.
Firebase QuerySnapshot has a docs property of type Array<QueryDocumentSnapshot<T>>. You can use that and the Array.find to search for the user. You should also await for bcrypt.compare while you search for user.
function authenticate({ username, password }) {
return users.then(async (querySnapshot) => {
const { usersDocs: docs } = querySnapshot;
const userDoc = usersDocs.find(doc => {
return doc.data().username === username;
});
if (userDoc) {
let user = doc.data();
const pwdCompareResult = await bcrypt.compare(password, user.password);
console.log(password);
console.log(user.password);
console.log(pwdCompareResult );
if (pwdCompareResult ) {
const token = jwt.sign({ sub: user.id }, config.secret);
const { password, ...userWithoutPassword } = user;
return {
...userWithoutPassword,
token
}
}
}
})
}
Please consider using Firebase Authentication instead
Your auth implementation is not reliable and it transfers sensitive data to every users device, like usersname and password hashes. Firebase has a very solid authentication system that you should be using.
I have two schemas, User, and Product. The User schema is where I store all the product ID and the number of items that the user added to the cart.
When the user makes a request to '/checkout' it should update the quantity and then remove it from the cart. I am having an issue when I checkout the quantity is not updating the quantity.
router.post('/checkout', auth, catchAsync(async (req, res) => {
const user = await User.findById(req.session.userId);
const err = [];
if (user && user.products.length > 0) {
user.products.map(async (product) => {
let total = 0;
const p = await Product.findById(product.productID);
if (p.quantity > 0 && p.quantity > product.quantity) {
console.log('IN');
total = p.quantity - product.quantity;
console.log(total);
await Product.findOneAndUpdate({ _id: product.productID }, { $set: { quantity: total } });
} else {
err.push(`Item, ${p.name} is sold out`);
}
});
await User.findOneAndUpdate({ _id: req.session.userId }, { $set: { products: [] } });
if (err.length) {
return res.status(500).json({ message: err });
}
return res.status(200).json({ message: 'OK' });
}
return res.status(200).json({ message: 'Empty cart' });
}));
User Schema:
Product Schema:
I believe the problem in your code is at the user.products.map(...) function, because you never wait for all the promises you create in the map to resolve.
In other words, the map function returns an array of pending promises, but it will not wait for them to be done, and therefore the execution continues through the rest of the code reaching the res.status(...) before any of the code in map had been executed.
You have different options to solve it, but mainly you need to take care of the array of promises returned by the map function and wait for their completion, before you end your code. There is a very good explanation of how to handle this situation with async/await at Google Developers Web fundamentals guide.
I usually leverage Promise.all() function, which returns a single promise from the array of promises, and therefore you can wait until the code in map is executed in parallel for each item in the array (i.e. product in your case). You can read more about it at MDN documentation.
// ...
let promisesArray = user.products.map(async product => {...});
// promisesArray should look like: [Promise { <pending> }, Promise { <pending> }, … ]
// Using Promise.all we wait for each of them to be done in parallel
await Promise.all(promisesArray);
// Now you are certain the code in the map has been executed for each product
// ...
A good practice as well is to use try {} catch(err) {} block around the Promise.all() to handle cases of some promise being rejected.
I'm quite desperate, because of this annoying error. I can't find any information about that on the internet.
Actually my application works fine, but if I start multiple queries and wait for them with Promise.all(), the server crashes. But step by step:
In my index.js, I'm initializing the connection pool with 60 max. connections, and exporting the variable.
const pool = mariadb.createPool(config.mariaDB);
module.exports.pool = pool
Later I'm importing the index file as "app" and using app.pool.query(...) to query my database.
Next, I have a post request /getUsersGroups. A User can be a member of multiple groups, so I expect the response to be an array of objects. Each object contains information about the group. Moreover there is a members field inside this object. This field also is an array containing information about the members, so s.th. like that:
[
{
"groupId": 125758,
"title": "test",
"members": [
{userId: 5, name:Max, }, ...]
}, ...
]
Therefore I have a post request, which looks like that. I'm getting all group ids the user is a member. Then for each groupId, I call the method Group.groupInfo.
This returns a promise, therefore I'm collecting all promises in an array and wait for all of them, before sending the response.
router.post("/getUsersGroups", (req, res) => {
let userId = req.body.userId;
let result = []
let promises = []
Group.getGroupsByUser(userId) // this gets all group ids the user is a member
.then((item) => {
let groups = item.map(x => objectParser.parseUnderscoreToCamel(x))
for (let i = 0; i < item.length; i++) {
let groupId = item[i].groupId
promises.push( //adding promises
Group.groupInfo(groupId, userId)
.then((item) => {
result.push(item)
})
.catch((err)=>{console.log(err)}))
}
})
.then(() => {
// waiting for all promises to finish
return Promise.all(promises)
.then(() => {
res.status(200).json({groups: result})
})
})
.catch((err) => {
console.log(err)
}
})
}
So the Promise Group.groupInfo looks like this. It first selects the group information and then for each member inside the group it calls another Promise User.userInfo. The pattern is the same like in the method above. So I push the Promise to an array and wait for them to finish:
module.exports.groupInfo = (groupId, userId)=>{
let groupInfo = {}
let promises = []
return new Promise(((resolve, reject) => {
app.pool.query("SELECT* FROM Groups WHERE group_id=?", [groupId])
.then((item) =>{
groupInfo = item[0]
groupInfo["members"] = [];
return app.pool.query("SELECT user_id FROM Users_groups WHERE group_id=?", [groupId])
.then((item) =>{
let members = [] //contains all user ids
for(let i=0; i<item.length;i++){
members.push(item[i].userId)
}
members.forEach((memberId)=>{
// for each memberID call User.userInfo
promises.push(User.userInfo(userId,memberId)
.then((item)=>{
groupInfo.members.push(item)}))
}
})
})
})
.then(()=>{
// wait for all Promises
return Promise.all(promises)
})
.then(() =>{
return resolve(groupInfo)
})
.catch((err)=>{
return reject(err)
})
}))
}
User.userInfo itself, makes a query to the Database to collect the information about the user.
If I'm calling this, the server crashes and giving me this error:
activeConnections[conn.threadId] = conn;
TypeError: Cannot read property 'threadId' of undefined
at Pool.handleTaskQueue (****\node_modules\mariadb\lib\pool.js:431:30)
at _combinedTickCallback (internal/process/next_tick.js:132:7)
at process._tickCallback (internal/process/next_tick.js:181:9)
I guess the problem is somehow with the Promise.all() right?
But I cannot find any information about this error. I'm thankful for every hint!
My solution was not to use Promise.all but to call the Promises sequentially, but seems not right to me.
return groupsId.reduce((promise, groupId) => {
return promise.then(() => {
return Group.groupInfo(groupId, userId)
.then((item)=>{
result.push(item)
console.log(result)
})
.catch((err)=>{
throw err
})
})
}, Promise.resolve())
I cannot rely on this, as long as I don't understand the error.
Thanks!
Created an Rest Post-type API in nodeJS,in which :
I am executing two queries here.
1. Firstly Executing the query on answers table to fetch user-Id and answer detail as well in that table. // i have check in the my console they show me two user-Id
2.Second ,executing the query on users table to fetch users detail on the basis of user-Id that i pass in find function. // check my console they show me two answer object because i have two user-Id.
------------------ above process work fine ---------------------
Now, i got stuck because , i have two merge both result into one object.
I'm doing this but isn't work perfect.Help me out here..!!
My code : -
app.post('/getTopAns', function(req, res) {
console.log("inside getTopAns ");
var questionId=req.body.questionId;
mongoose.model('answers').find({
questionId:questionId,
compliance:"Y"
}, function(err, ansResult){
for (var i = 0;i<ansResult.length;i++) {
mongoose.model('users').findOne({
userId:ansResult[i].userId,
}, function(err,usrResult){
var obj = {
followerLength : usrResult.follower.length,
upvote : ansResult[i].upvote
}
})
console.log(obj);
}
});
})
Maybe you should first log ansResult to see if db is returning this. If it is, then check usrResult. If both are returned, I usually use lodash's assign or merge to merge two objects into one
I recommend using promises instead of callback to handle async easier with node.js. The flow is to get the first call result, map the array of result into a list of promises which we can wait for the result by using Promise.all. Finally map the second results which the first list.
app.post("/getTopAns", function(req, res) {
console.log("inside getTopAns ");
var questionId = req.body.questionId;
mongoose.model("answers")
.find({
questionId: questionId,
compliance: "Y"
})
.then(ansResult => {
const promises = ansResult.map(ans => {
return mongoose.model("users")
.findOne({ userId: ansResult[i].userId })
})
return Promise.all(promises)
.then(usrResults => {
return usrResults.map((usrResult, i) => {
followerLength: usrResult.follower.length,
upvote: ansResult[i].upvote
})
})
})
.then(results => {
console.log('got all results here', results)
})
});
It can be even better using async/await.
app.post("/getTopAns", async (req, res) => {
console.log("inside getTopAns ");
var questionId = req.body.questionId;
const ansResult = mongoose.model("answers")
.find({
questionId: questionId,
compliance: "Y"
})
const promises = ansResult.map(ans => {
return mongoose.model("users")
.findOne({ userId: ansResult[i].userId })
})
const usrResults = await Promise.all(promises)
const results = usrResults.map((usrResult, i) => {
followerLength: usrResult.follower.length,
upvote: ansResult[i].upvote
})
console.log('got all results here', results)
});
More information can be found here: https://medium.com/#ThatGuyTinus/callbacks-vs-promises-vs-async-await-f65ed7c2b9b4