I have a function that list all users and role refrenced to group. Now i have other function that take user role refid and returns group name. While trying to return name i got promise pending state.
function getAll() {
var deferred = Q.defer();
db.users.find().toArray(function(err, users) {
if (err) deferred.reject(err.name + ': ' + err.message);
// return users (without hashed passwords)
users = _.map(users, function(user) {
//console.log(user);
return _.omit(user, ['hash']);
});
users = _.map(users, function(user){
refId = {}= user['role'][0]['oid']['_id'];
//console.log(typeof refId);
user = _.omit(user, ['role']);
user.role = userRole.userRole(refId).then(function(err,rid){
if(err){
deferred.reject(err.name+':'+err.message);
}
deferred.resolve();
console.log(deferred.resolve(rid));
return deferred.promise;
console.log(deferred.promise);
});
return user;
//console.log(user);
})
// getRefId(users)
//console.log(users);
deferred.resolve(users);
});
function userRole(rid){
return new Promise((resolve, reject) => {
db.groups.findOne({"_id":rid}, function(err, doc){
if(err){
reject(err.name + ':' + err.message);
}
if(doc){
resolve({"name": doc.name});
//console.log(doc.name);
}
})
})
}
You you want to use Promises with Mongoose or the native Mongo driver for Node which you seem to be doing here, you don't have to use new Promise() everywhere. You can use the promises directly.
See the docs:
The official MongoDB Node.js driver provides both callback based as well as Promised based interaction with MongoDB allowing applications to take full advantage of the new features in ES6.
https://mongodb.github.io/node-mongodb-native/
Mongoose async operations, like .save() and queries, return Promises/A+ conformant promises. This means that you can do things like MyModel.findOne({}).then() and yield MyModel.findOne({}).exec() (if you're using co).
http://mongoosejs.com/docs/promises.html
Instead of this:
function userRole(rid){
return new Promise((resolve, reject) => {
db.groups.findOne({"_id":rid}, function(err, doc){
if(err){
reject(err.name + ':' + err.message);
}
if(doc){
resolve({"name": doc.name});
//console.log(doc.name);
}
})
})
}
you should be able to use:
function userRole(rid){
return db.groups.findOne({ _id: rid });
}
or:
const userRole = rid => db.groups.findOne({ _id: rid });
Now, in your version there is one problem - the promise will never get resolved if there is no error but the returned doc is falsy, which can happen. But there is no point in creating your own promises if the methods that you call return promises in the first place.
Even if you want custom error messages, you can still use:
function userRole(rid){
return db.groups.findOne({ _id: rid })
.catch(function (err) {
return Promise.reject(err.name + ':' + err.message);
});
}
or this with more modern syntax:
const userRole = rid => db.groups.findOne({ _id: rid })
.catch(err => Promise.reject(`${err.name}:${err.message}`));
Related
I am building a MERN stack social media application. On the application, a user can have a profile with posts which can either be a photo or a video.
My photos are stored in the posts collection, however, videos are stored in a collection named media.
When the user wants to view their posts, I have a function that gets all data from both collections, sorts them by their date of creation and returns them to the frontend. This has been working fine until the user builds up a large number of photos/videos, and now MongoDB won't allow that user to make the request anymore as it takes too much RAM.
I want to implement a lazy-load onto this so I'm not requesting all this data that the user doesn't even need, and I know how to do this using a single collection, however, I'm not sure how I would go about doing it when using two collections.
I know I would limit each collection to 2 objects at one time and add a skip to each to request the next two objects, but I don't know how to keep track of which needs to come next, a photo or a video?
My current code:
//First function, called by route
const listPostAndMediaByUser = (req, res) => {
sortMediaAndPosts(req)
.then(function(postsAndMedia) {
return res.json(postsAndMedia);
})
.catch(function(error) {
console.log("Error getting posts", error)
return res.status(400).json({
error: errorHandler.getErrorMessage(error)
});
});
};
//Sorting function
//This function runs getPosts, and getMedia
//And sorts them by their creation date
const sortMediaAndPosts = function(req) {
return new Promise(async function(resolve, reject) {
let postsAndMedia = [];
try {
const posts = await getPosts(req);
const media = await getMedia(req);
postsAndMedia = [...posts, ...media].sort(
(a, b) => new Date(b.created) - new Date(a.created)
);
} catch (error) {
console.log('Error: ', error);
reject(error);
}
resolve(postsAndMedia);
});
};
//Get posts function
const getPosts = function(req) {
return new Promise(async function(resolve, reject) {
try {
Post.find({ postedBy: req.profile._id })
.limit(2)
.select('-photo')
.populate('postedBy', '_id name')
.populate('comments.postedBy', '_id name')
.sort('-created')
.exec((err, posts) => {
if (err) reject(err);
else resolve(posts);
});
} catch(e) {
console.log('error!', e)
reject(e);
}
});
};
//Get Media function
const getMedia = function (req) {
return new Promise(async function(resolve, reject) {
Media.find({postedBy: req.profile._id})
.limit(2)
.populate('postedBy', '_id name')
.populate('comments.postedBy', '_id name')
.sort('-created')
.exec((err, media) => {
if(err) reject(err)
else resolve(media)
})
})
}
Any input would be greatly appreciated.
Thanks
Your schemas for Post and Media look very similar. You should consider merging them into a single schema. This would resolve your problem.
If you don't want to change your schema you should look into the mongodb aggregation pipeline which allows you to join data from multiple collections (using $lookup).
I am trying to use Promises instead of Callback in my Lambda Function in NodeJs 4.3. What I'm trying to do is read a config file from S3 and use the configuration to connect to a DB and execute a SELECT statement on a table. When I test the function, I don't get any errors in the console.
I have defined my function as follows:
function setUpConnection(response) {
console.log("S3 Response " + JSON.stringify(response));
return new Promise(function(resolve, reject) {
config = response.Body.toString('utf-8');
config = JSON.parse( config );
// set up connection from S3 config file
var con = mysql.createConnection({
host: config.hostaddress,
user: config.user,
password: config.pass,
database: config.dbname
});
console.log("connection " + JSON.stringify(con));
console.log("config " + JSON.stringify(config));
// create connection
con.connect(function(err){
if(err){
// Output connection details for debugging
console.log('Error connecting to DB');
return Promise.reject(new Error(output));
}
});
// Run Select Query
con.query("SELECT * FROM goodsreceiptheader WHERE invoiceKey = ?", [invoicekey], function(err,res){
if(err){
console.log(err);
con.end();
return Promise.reject(new Error(output));
}
if ( res.length <= 0 ){
console.log('Response Object ' + JSON.stringify(res));
con.end();
return Promise.reject(new Error(output));
}
resolve(res);
})
})
}
The function is being called by this function:
// Setup goodsreceipt info
var goodsreceipt = data.goodsreceipt;
getObjectPromise = s3bucket.getObject(params).promise()
getObjectPromise
.then(setUpConnection)
.then(validateRecord)
.catch(callback(null, Error))
When I execute this code, I am only seeing the result of the code
console.log("S3 Response " + JSON.stringify(response));
on the second line. Is there a problem with the way I configured setUpConnection?
You're using rejection in a wrong way.
return Promise.reject(new Error(output));
Should be replaced with
reject(new Error(output));
return;
Also catch call looks strange: it calls callback immediately. Usually catch block looks like that:
somePromise.catch((error) => {
console.error(error); // If output required.
callback(error); // If callback provided.
});
Move con.query call inside of con.connect callback.
First of all, from where output is coming? I don't see it anywhere defined.
Second of all it is considered as good practice to always throw/reject with an instance of Error, in your example i guess, at least by variable name, output is not an instance of Error. Third thing in general only valid use case in my opinion of using new Promise constructor is when you want to transform callback api to promise which you are doing here (which is good) or you when deal with setTimeout/setInterval
Anyway you are using Promise.reject where you have reject in scope already which is not how you should reject your promise in this case. Next thing is that you are using con.query outside of callback that is provided to con.connect which effectively means you are calling query before connection was successfully established, try with something like:
return new Promise(function(resolve, reject) {
...
con.connect(function(err){
if(err){
return reject(new Error('...'));
}
// Run Select Query
con.query(..., ..., function (err,res) {
if(err){
...
return reject(new Error('...'));
}
if (res.length <= 0 ){
...
return reject(new Error(output));
}
return resolve(res);
})
});
})
Few sides notes:
1. Check does this lib have promise api, probably it should in that way you dont need to promisify things around on your own.
2. If it doesnt have you can always use libs like bluebird to promisfy code for you
You rejection of promise is wrong
Try in this way
function setUpConnection(response) {
console.log("S3 Response " + JSON.stringify(response));
return new Promise(function(resolve, reject) {
config = response.Body.toString('utf-8');
config = JSON.parse( config );
// set up connection from S3 config file
var con = mysql.createConnection({
host: config.hostaddress,
user: config.user,
password: config.pass,
database: config.dbname
});
console.log("connection " + JSON.stringify(con));
console.log("config " + JSON.stringify(config));
// create connection
con.connect(function(err){
if(err){
// Output connection details for debugging
console.log('Error connecting to DB');
return reject(err);
}
});
// Run Select Query
con.query("SELECT * FROM goodsreceiptheader WHERE invoiceKey = ?", [invoicekey], function(err,res){
if(err){
console.log(err);
con.end();
return reject(err);
}
if ( res.length <= 0 ){
console.log('Response Object ' + JSON.stringify(res));
con.end();
return reject('response less 0');
}
resolve(res);
})
})
}
I would like to know if it's possible to run a series of SQL statements and have them all committed in a single transaction.
The scenario I am looking at is where an array has a series of values that I wish to insert into a table, not individually but as a unit.
I was looking at the following item which provides a framework for transactions in node using pg. The individual transactions appear to be nested within one another so I am unsure of how this would work with an array containing a variable number of elements.
https://github.com/brianc/node-postgres/wiki/Transactions
var pg = require('pg');
var rollback = function(client, done) {
client.query('ROLLBACK', function(err) {
//if there was a problem rolling back the query
//something is seriously messed up. Return the error
//to the done function to close & remove this client from
//the pool. If you leave a client in the pool with an unaborted
//transaction weird, hard to diagnose problems might happen.
return done(err);
});
};
pg.connect(function(err, client, done) {
if(err) throw err;
client.query('BEGIN', function(err) {
if(err) return rollback(client, done);
//as long as we do not call the `done` callback we can do
//whatever we want...the client is ours until we call `done`
//on the flip side, if you do call `done` before either COMMIT or ROLLBACK
//what you are doing is returning a client back to the pool while it
//is in the middle of a transaction.
//Returning a client while its in the middle of a transaction
//will lead to weird & hard to diagnose errors.
process.nextTick(function() {
var text = 'INSERT INTO account(money) VALUES($1) WHERE id = $2';
client.query(text, [100, 1], function(err) {
if(err) return rollback(client, done);
client.query(text, [-100, 2], function(err) {
if(err) return rollback(client, done);
client.query('COMMIT', done);
});
});
});
});
});
My array logic is:
banking.forEach(function(batch){
client.query(text, [batch.amount, batch.id], function(err, result);
}
pg-promise offers a very flexible support for transactions. See Transactions.
It also supports partial nested transactions, aka savepoints.
The library implements transactions automatically, which is what should be used these days, because too many things can go wrong, if you try organizing a transaction manually as you do in your example.
See a related question: Optional INSERT statement in a transaction
Here's a simple TypeScript solution to avoid pg-promise
import { PoolClient } from "pg"
import { pool } from "../database"
const tx = async (callback: (client: PoolClient) => void) => {
const client = await pool.connect();
try {
await client.query('BEGIN')
try {
await callback(client)
await client.query('COMMIT')
} catch (e) {
await client.query('ROLLBACK')
}
} finally {
client.release()
}
}
export { tx }
Usage:
...
let result;
await tx(async client => {
const { rows } = await client.query<{ cnt: string }>('SELECT COUNT(*) AS cnt FROM users WHERE username = $1', [username]);
result = parseInt(rows[0].cnt) > 0;
});
return result;
i am pushing errors in array by using promises but before it push errors in array it check the condition for insert data like in else i am inserting data.
var errorsArr= [];
var username = new Promise(function(resolve,reject){
User.findOne({ "username": req.body.username },function(err,user){
if(err)
reject(err);
if(user) {
resolve(1);
}else{
resolve(0);
}
});
});
username.then(function(data){
if( data == 1 )
errorsArr.push({"msg": "Username already been taken."});
}).catch(function(err){
console.log(err);
});
var email = new Promise(function(resolve,reject){
User.findOne({ "email": req.body.email },function(err,user){
if(err)
reject(err);
if(user) {
resolve(1);
}else{
resolve(0);
}
});
});
email.then(function(data){
if( data == 1 )
errorsArr.push({"msg": "email already been taken."});
}).catch(function(err){
console.log(err);
});
if(errorsArr.length >0)
{
req.session.error = errorsArr;
return res.redirect('/auth/Registration');
}
else {
var newUser = new User();
newUser.username = req.body.username;
newUser.password = req.body.password;
newUser.sex = req.body.sex;
newUser.email = req.body.email;
newUser.phoneNumber = req.body.phoneNumber;
newUser.age = req.body.age;
newUser.designation = req.body.designation;
newUser.timing = req.body.timing;
var CurrentDate = moment.tz(new Date(req.body.joiningDate), "Asia/Karachi").unix();
newUser.joiningDate = CurrentDate;
newUser.save(function (err, user) {
if (!err)
return res.redirect('/auth/Login');
});
}
can you please help me to do it in better way i am new in node.js. Thanks in advance.
Promise.all is your solution. It waits for all the promises.
Promise.all([p1,p2,..]).then(function() {
// all loaded
}, function() {
// one or more failed
});
Reference Link:
http://www.html5rocks.com/en/tutorials/es6/promises/
You're mixing asynchronous and synchronous code.
Synchronous:
var errorsArr= [];
Asynchronous:
var username = new Promise(...{
...
});
username.then(..
errorsArr.push(...);
})
Synchronous:
if (errorsArr.length > 0) {
...
The asynchronous part in the middle (where you do errorsArr.push(…)) doesn't actually happen before your synchronous code at the end (if (errorsArr.length > 0)).
You need to convert all your synchronous code to asynchronous as well, if you want it to execute in succession to the other async code.
Other answers have already explained that how, which is by using Promise.all
I would also like to suggest using Bluebird Promise library which offers promisification so you can just promisify your mongoose models, instead of manually creating promises for every operation.
Promise.promisifyAll(User);
var username = User.findOneAsync(...); // the new "findOneAsync" method returns a promise
username.then(data => ...)
You might also wanna checkout the upcoming ES2016 additions to javascript that further simplify promises with async/await. With it you can get rid of .then(..) entirely and just write:
app.get('/', async function(req, res) { // < async function
try {
var username = await User.findOneAsync(...); // await statements
var email = await User.findOneAsync(...);
// do stuff
} catch(err){
// handle errors
}
});
Checkout this great article on async/await. Although this requires you add an extra transpilation step by using Babel or Typescript. There's also the recently released Microsoft's fork of Node that ships with chakra engine (instead of V8) which already supports async/await as well.
Here is an example
Promise.all([
Reservation.find({'parking.owner': req.payload.id}).exec(),
Reservation.count({'parking.owner': req.payload.id}).exec(),
Reservation.aggregate([
{ $match: {
_id: req.payload.id
}},
{ $group: {
_id: "$_id",
total: { $sum: "$price" }
}}
])
]).then(results=>{
const list = results[0]
const count = results[1]
const sum = results[2]
res.status(200).json({list,count,sum});
})
after all Promises have been done ... use Promise.all
the checking for errors at the bottom of your code could be written:
Promise.all([username, email])
.then(function(results) {
if(errorsArr.length >0)
{
...
}
else {
...
}
});
Quickest and easiest change to your code - refactoring your code can make it even sleeker
#Jaromanda X answer is correct if there is username and email checks are independent and no order has to be followed. Else, you have to do a promise chain.
This is my code:
var thisValue = new models.Value({
id:id,
title:title //this is a unique value
});
console.log(thisValue);
thisValue.save(function(err, product, numberAffected) {
if (err) {
if (err.code === 11000) { //error for dupes
console.error('Duplicate blocked!');
models.Value.find({title:title}, function(err, docs)
{
callback(docs) //this is ugly
});
}
return;
}
console.log('Value saved:', product);
if (callback) {
callback(product);
}
});
If I detect that a duplicate is trying to be inserted, i block it. However, when that happens, i want to return the existing document. As you can see I have implemented a string of callbacks, but this is ugly and its unpredictable (ie. how do i know which callback will be called? How do i pass in the right one?). Does anyone know how to solve this problem? Any help appreciated.
While your code doesn't handle a few error cases, and uses the wrong find function, the general flow is typical giving the work you want to do.
If there are errors other than the duplicate, the callback isn't called, which likely will cause downstream issues in your NodeJs application
use findOne rather than find as there will be only one result given the key is unique. Otherwise, it will return an array.
If your callback expected the traditional error as the first argument, you could directly pass the callback to the findOne function rather than introducing an anonymous function.
You also might want to look at findOneAndUpdate eventually, depending on what your final schema and logic will be.
As mentioned, you might be able to use findOneAndUpdate, but with additional cost.
function save(id, title, callback) {
Value.findOneAndUpdate(
{id: id, title: title}, /* query */
{id: id, title: title}, /* update */
{ upsert: true}, /* create if it doesn't exist */
callback);
}
There's still a callback of course, but it will write the data again if the duplicate is found. Whether that's an issue is really dependent on use cases.
I've done a little clean-up of your code... but it's really quite simple and the callback should be clear. The callback to the function always receives either the newly saved document or the one that was matched as a duplicate. It's the responsibility of the function calling saveNewValue to check for an error and properly handle it. You'll see how I've also made certain that the callback is called regardless of type of error and is always called with the result in a consistent way.
function saveNewValue(id, title, callback) {
if (!callback) { throw new Error("callback required"); }
var thisValue = new models.Value({
id:id,
title:title //this is a unique value
});
thisValue.save(function(err, product) {
if (err) {
if (err.code === 11000) { //error for dupes
return models.Value.findOne({title:title}, callback);
}
}
callback(err, product);
});
}
Alternatively, you could use the promise pattern. This example is using when.js.
var when = require('when');
function saveNewValue(id, title) {
var deferred = when.defer();
var thisValue = new models.Value({
id:id,
title:title //this is a unique value
});
thisValue.save(function(err, product) {
if (err) {
if (err.code === 11000) { //error for dupes
return models.Value.findOne({title:title}, function(err, val) {
if (err) {
return deferred.reject(err);
}
return deferred.resolve(val);
});
}
return deferred.reject(err);
}
return deferred.resolve(product);
});
return deferred.promise;
}
saveNewValue('123', 'my title').then(function(doc) {
// success
}, function(err) {
// failure
});
I really like WiredPrairie's answer, but his promise implementation is way too complicated.
So, I decided to add my own promise implementation.
Mongoose 3.8.x
If you're using latest Mongoose 3.8.x then there is no need to use any other promise module, because since 3.8.0 model .create() method returns a promise:
function saveNewValue(id, title) {
return models.Value.create({
id:id,
title:title //this is a unique value
}).then(null, function(err) {
if (err.code === 11000) {
return models.Value.findOne({title:title}).exec()
} else {
throw err;
}
});
}
saveNewValue('123', 'my title').then(function(doc) {
// success
console.log('success', doc);
}, function(err) {
// failure
console.log('failure', err);
});
models.Value.findOne({title:title}).exec() also returns a promise, so there is no need for callbacks or any additional casting here.
And if you don't normally use promises in your code, here is callback version of it:
function saveNewValue(id, title, callback) {
models.Value.create({
id:id,
title:title //this is a unique value
}).then(null, function(err) {
if (err.code === 11000) {
return models.Value.findOne({title:title}).exec()
} else {
throw err;
}
}).onResolve(callback);
}
Previous versions of Mongoose
If you're using any Mongoose version prior to 3.8.0, then you may need some help from when module:
var when = require('when'),
nodefn = require('when/node/function');
function saveNewValue(id, title) {
var thisValue = new models.Value({
id:id,
title:title //this is a unique value
});
var promise = nodefn.call(thisValue.save.bind(thisValue));
return promise.spread(function(product, numAffected) {
return product;
}).otherwise(function(err) {
if (err.code === 11000) {
return models.Value.findOne({title:title}).exec()
} else {
throw err;
}
});
}
I'm using nodefn.call helper function to turn callback-styled .save() method into a promise. Mongoose team promised to add promises support to it in Mongoose 4.x.
Then I'm using .spread helper method to extract the first argument from .save() callback.