Async / Await with NodeJS + Mongoose doesn't wait - node.js

Cannot get the async / await functions to work properly in my card game app.
(a) I get the 201 response with no data.
(b) the deck document seems to be created afterwards, with the players field an empty array, indicating it is done after the deck is saved to the mongoDB
Below is my code. Any help is appreciated.
router.js
router.post('/game', (req, res, next) => {
try {
const { cards, playerNames, attributes } = req.body;
const newDeck = deck.start(cards, playerNames, attributes);
res.status(201).send(newDeck);
} catch (err) {
next(err);
};
});
/services/deck.js
exports.start = async (cards, playerNames, attributes) => {
try {
const users = await user.create(playerNames);
const deck = new Deck({
cards,
attributes,
players: users
});
return await deck.save((err, newDeck) => {
if (err) console.log(err);
console.log('RESULT', newDeck);
});
} catch (err) {
console.log(err);
}
};
/services/user.js
exports.create = async (users) => {
if (users.constructor === String) {
const user = new User({displayname: users});
return await user.save((err, newUser) => {
if (err) console.log(err);
console.log('NEW USERS ', user);
return newUser;
});
} else if (users.constructor === Array) {
let userList = [];
await users.forEach(name => {
const user = new User({displayname: name.toString()});
return user.save((err, newUser) => {
if (err) {
console.log(err);
} else {
userList.push(newUser);
return newUser;
}
});
});
console.log('NEW USERS ', userList);
return userList;
};
};

I am not familiar how you're handling promises,
but forEach is not promise-aware, that's how it has been designed, so it will not handle your asynchronous code properly
replace it with normal for loop or for-of loop, and add the await keyword in front of the user.save() method

Related

How to populate an id in an array of objects firebase

committeeHead is a reference in collection users. I want to populate this id to get the specific data.
tried using Promise.all but I don't completely understand it and it isn't working for me.
const getAllCommittees = async (req, res, next) => {
try {
const committees = await db.collection("committees").get();
const committeesArray = [];
committees.forEach((doc) => {
committeesArray.push({ id: doc.id, ...doc.data() });
});
const committeesWithUsers = await Promise.all(
committeesArray.map((committee) => {
const user = db.collection("users").doc(committee.committeeHead).get();
return {
committee,
user,
};
})
);
res.json(committeesWithUsers);
} catch (err) {
console.log(err);
next(err);
}
};

How to return data i got from database, from model back to controller

I am using Nodejs and cloud firestore as a database, i got the right data from database (logout) in model but after returning it back to controller i can not got it
// this is controller:
CheckPhoneNumber = (req, res) => {
// getting data from json
var json = {
// this is the entry ==> PhoneNumber : 123456789
PhoneNumber: req.body.PhoneNumber
}
// calling model function
console.log('this is from controller before calling database');
var user = model.CheckPhoneNumber(json.PhoneNumber)
.then(function () {
console.log('this is from controller after calling database');
console.log(user);
return user;
}).catch(err => {
return 'Error in controller awaiting ', err;
});
// outputting to Postman
return res.json(Promise.resolve(user));
}
and that controller calls the below model
// this is model :
// importing configure of database
const config = require('../config/main');
// this is for updates from google to firestore
config.db.settings({ timestampsInSnapshots: true });
// setting root of users database
const usersRoot = config.db.collection( my root here );
// takes json and return that user which phone number belongs to (actually it gets user by phone number )
async function CheckPhoneNumber(PhoneNumber) {
// getting data from database
var user;
user = await usersRoot.where('PhoneNumber', '==', PhoneNumber).get()
.then(async snapshot => {
if (snapshot.empty) {
return 'No matching documents.';
}
return await snapshot.forEach(async doc => {
console.log('your user is : ');
user = await doc.data();
console.log(user);
console.log('this is from model after calling database');
return await user;
});
}).catch(err => {
// console.log('Error getting documents',err);
return 'Error getting documents', err;
});
console.log('this is user befor returning : ');
console.log(user);
return user;
}
and this is the output of console in windows OS
this is from controller before calling database
this is from model after calling database
{ ... there is here some data ... }
this is from controller after calling database
Promise { <'pending'> }
I expect to get the data i returned from controller speicially after i await it to get data from database in the last line of console
It's because your user is a promise object.
You may want to respond in this block like,
model.CheckPhoneNumber(json.PhoneNumber)
.then(function (user) {
console.log('this is from controller after calling database');
console.log(user);
res.json(user)
})
.catch(err => {
res.json('error')
});
Or, you do it this way:
// make you route function async
CheckPhoneNumber = async (req, res) => {
...
// then you could
try {
const user = await model.CheckPhoneNumber(json.PhoneNumber)
res.json(user)
} catch (e) {
res.json('error')
}
change your model to:
async function CheckPhoneNumber(PhoneNumber) {
// getting data from database
try {
var snapshot = await usersRoot.where('PhoneNumber', '==', PhoneNumber).get()
if (snapshot.empty)
return "No"
var datas = [];
snapshot.forEach(doc => {
datas.push(doc.data())
})
return datas;
} catch (e) {
console.error(e);
}
}

Correct way to figure out what rejection a promise had?

I have an API / express router:
router.post("/signup", async function (req, res) {
try {
var user = await controllers.user.register(req.body.username, req.body.password);
req.session.user = user;
res.json(user);
} catch (e) {
res.status(500).json("DB Error");
}
});
Currently, on error, it returns 500 DB error. This is my controller:
function register(username, password) {
return new Promise((resolve, reject) => {
User.findOne({ username: username }).lean().exec((e, doc) => {
if (e) reject(e);
if (doc) {
reject("Username already exists.");
} else {
var user = new User({ username, password: hash(password) });
user.save((e) => {
if (e) reject(e);
else {
delete user.password;
resolve(user);
}
});
}
});
});
}
What's the right way to return a 400 if username already exists, and a 500 if it was a database error?
Mongoose already uses promises, the use of new Promise is promise construction antipattern.
Express doesn't have the concept of controllers, there are only route handlers and middlewares. Since register should be very aware of the way it will be used in a response, there may be no need for another level of abstraction above route handler. There will be no problem when a function has access to handler parameters and can form a response in-place.
It can be:
router.post("/signup", async function (req, res) {
try {
const { body, password } = req.body;
const user = await User.findOne({ username: username }).lean();
if (user) {
res.status(400).json("Username already exists");
} else {
...
res.json(user);
}
} catch (e) {
res.status(500).json("DB Error");
}
});
In case route handler needs to be reused in multiple places with some variations, it could be refactored to higher-order function or some other helper that is aware of original req and res parameters.
You can change the way you are rejecting the Promise. I'd suggest something like:
function register(username, password) {
return new Promise((resolve, reject) => {
User.findOne({ username: username }).lean().exec((e, doc) => {
if (e) reject(500);
if (doc) {
reject(400);
} else {
var user = new User({ username, password: hash(password) });
user.save((e) => {
if (e) reject(500);
else {
delete user.password;
resolve(user);
}
});
}
});
});
}
And in the route:
router.post("/signup", async function (req, res) {
try {
var user = await controllers.user.register(req.body.username, req.body.password);
req.session.user = user;
res.json(user);
} catch (e) {
res.status(e).json(e == 400 ? "Username already exists." : "DB Error");
}
});

nested Promise & reject

I have some problem with promise functions, my app has this structure:
- routes
- service
- db
db is a class initialized when the application start and where I created some wrapper function for insert/find/ecc..., service is a layer between the route and db, here I do most of the work. My problem is that with the code below if a user already exist I want to throw an error or reject the promise but when I try to do something like this I get
Cannot read property 'then' of undefined
where is the error?
This is my resource:
router.put('/', (req, res, next) => {
bcrypt.hash(req.body.password, 10)
.then(function (hash) {
req.body.password = hash;
service.addUser(req.body)
.then((user) => {
return res.json(user);
})
.catch((err) => {
return res.json(err);
});
})
.catch((err) => {
return res.json(err);
});
});
This is the service:
getBy(query) {
return this.mongo.find(query);
}
addUser(data) {
if(!data.email) {
return Promise.reject('email_missing');
}
const self = this;
self.getBy({ email: data.email })
.then((user) => {
if(user.length) {
return Promise.reject('user_exist');
}
return self.mongo.insert(data)
})
.catch((err) => {
return Promise.reject(err);
});
}
and this is the db connection:
find(query) {
const self = this;
return new Promise((resolve, reject) => {
self.collection.find(query).toArray((err, res) => {
if (err) {
self.logger.info('Mongo::find error', err);
reject(err);
} else {
self.logger.info('Mongo::find', query);
resolve(res);
}
});
});
}
insert(data) {
const self = this;
return new Promise((resolve, reject) => {
self.collection.insert(data, (err, res) => {
if (err) {
self.logger.info('Mongo::insert error', err);
reject (err)
} else {
self.logger.info('Mongo::insert', res);
resolve(res)
}
});
});
}
many thanks!
The addUser function does not return a Promise. The code should look like this:
addUser(data) {
if (!data.email) {
return Promise.reject('email_missing');
}
const self = this;
return self.getBy({
email: data.email
})
.then((user) => {
if (user.length) {
return Promise.reject('user_exist');
}
return self.mongo.insert(data)
})
.catch((err) => {
return Promise.reject(err);
});
}
The .catch block here does not make sense because it just contains return Promise.reject(err) so you can remove it:
addUser(data) {
if (!data.email) {
return Promise.reject('email_missing');
}
const self = this;
return self.getBy({
email: data.email
})
.then((user) => {
if (user.length) {
return Promise.reject('user_exist');
}
return self.mongo.insert(data)
});
}
In the router you also have to return the Promise in the .then and you can remove one .catch block:
router.put('/', (req, res, next) => {
bcrypt.hash(req.body.password, 10)
.then(function(hash) {
req.body.password = hash;
return service.addUser(req.body) // return the Promise ehre
})
// the then can be move out here, to avoid nesting
.then((user) => {
return res.json(user);
})
// only on catch is required
.catch((err) => {
return res.json(err);
});
});
An additional note, you should always reject with a real error. So it would be better to write, Promise.reject(new Error('user_exist'))
Nesting promises is an anti-pattern.
See item #2 in Promise Patterns & Anti-Patterns
It's considered an anti-pattern because it reduces understandability and overly complicates the call stack making debugging (more of) a nightmare.
So rather than:
bcrypt.hash(req.body.password, 10)
.then(function (hash) {
req.body.password = hash;
service.addUser(req.body) // ANTI-PATTERN
.then((user) => {
return res.json(user); // [1]
})
.catch((err) => {
return res.json(err); // [2]
});
})
.catch((err) => {
return res.json(err);
});
});
Do this instead:
const SALT_ROUNDS = 10
app.get(URL, function(req, res) {
function setHashedPassword() {
// TODO first test for existence of password in req.body
return bcrypt.hash(req.body.password, SALT_ROUNDS)
.then(hash => req.body.password = hash)
}
function addUser() {
return service.addUser(req.body)
}
Promise.all([ setHashedPassword(), addUser() ])
.then((results) => {
const user = results[1]
res.json(user)
})
.catch((err) => {
res.json(err)
})
})
Note that, at [1] and [2] in the OP's code, it makes no sense to return anything since there is no active context to which you can return a value.
I'd also respond with an object like:
res.json({ok:true, user:user})
and
res.json({ok:false, error:err})
So you can check for success or failure in the client.
Yes, I know you might believe that ok is redundant here, but it's good practice to standardize on a single result value so you don't have to first test for the existence of error before you check for the existence of user.

the variable i is not filled

I have declare the variable first. but if I do console.log(userinsertData) outside looping variable still not fill.
what i should do for solving this problem?
here my code:
var User = require('../models/user');
module.exports = {
myaction: function(req, res, next) {
var data = req.body,
userinsertData = [];
try {
data.forEach(function(item, index) {
var userdata = new User();
userdata.name = item.name;
userdata.age = item.age;
userdata.sex = item.sex;
userdata.save(function(err, data) {
if (err) {
res.send(err)
} else {
userinsertData.push(data);
}
});
})
} catch (e) {
res.json({
message: 'data not valid'
})
}
console.log(userinsertData);
res.json({
message: 'musician created!',
data: userinsertData
});
}
};
you should solve the problem as
async.eachSeries(data, function (info, callback) {
//here process your data and call callback() for next iteration
}, function (err) {
if (err) {
//this will be called after all iterations and in case of error
}else{
console.log('Well done :-!');
//this will be called after all interations successfully
}
});
this problem you are facing is because of asynchronous nature of nodejs and async helps you to introduce blocking.
Don't forget to include async
Use promise
var User = require('../models/user');
module.exports = {
myaction: function(req, res, next) {
var data = req.body,
userinsertData = [];
new Promise(function(resolve, reject) {
data.forEach(function(item, index) {
var userData = new User(item);
userData.save(function(err, data) {
// if error, reject
if(err) return reject(err);
// we have proceed all items in data, resolve it
else if(data.length - 1 === index) return resolve(userinsertData);
// not finished yet, keep proceeding
else userinsertData.push(data);
});
}).then(function(successResult) {
res.json({
message: 'musician created!',
data: successResult
});
}, function(errorResult) {
res.json({
message: 'data not valid'
});
});
}
};
Use callbacks
var User = require('../models/user');
module.exports = {
myaction: function(req, res, next) {
var data = req.body,
userinsertData = [];
function saveUser(callback) {
data.forEach(function(item, index) {
var userData = new User(item);
userData.save(function(err, data) {
// if error, return callback with error
if(err) return callback(err);
// we have proceed all items in data, return data
else if(data.length - 1 === index) callback(null, userinsertData);
// not finished yet, keep proceeding
else userinsertData.push(data);
});
}
saveUser(function(err, users) {
if(err) return res.json({message: 'data not valid'});
res.json({
message: 'musician created!',
data: users
});
});
}
};
This is what async package does internally

Resources