How to confirm if update succeeds using mongoose and bluebird promise - javascript

I'm using bluebird and mongoose for a node page.
I want to check if the update is successful before sending data back to clients via socket.js.Here's the part of the code that I can't figure out:
.then(function(a) {
var g = collection3.update({
_id: a.one[0]._id
}, {
$set: {
avg: a.one[0].avg
}
}).function(err, d) {
if (!err) {
return 1; // Here's the problem
}
}) return {
updated: g,
info: a
};
}).then(function(c) {
console.log(c.updated); // I can't get the `1` value
if (c == 1) {
io.sockets.in('index|1').emit("estimate", c.three);
}
})
Does mongoose return a success message after update? I can't return 1 from the update query and pass it to the next then function, instead, I'm getting this object:
{ _mongooseOptions: {},
mongooseCollection:
{ collection:
{ db: [Object],
collectionName: 'table',
internalHint: null,
opts: {},
slaveOk: false,
serializeFunctions: false,
raw: false,
pkFactory: [Object],
serverCapabilities: undefined },
opts: { bufferCommands: true, capped: false },
name: 'table',
conn:....
Here's the full code:
socket.on("input",function(d){
Promise.props({
one: collection2.aggregate([
{
$match:{post_id:mongoose.Types.ObjectId(d.id)}
},
{
$group:{
_id:"$post_id",
avg:{$avg:"$rating"}
}
}
]).exec();
}).then(function(a){
var g = collection3.update({_id:a.one[0]._id},{$set:{avg:a.one[0].avg}}).function(err,d){
if(!err){
return 1; // Here's the problem
}
})
return {updated:g,info:a};
}).then(function(c){
console.log(c.updated); // I can't get the `1` value
if(c.updated == 1){
io.sockets.in('index|1').emit("estimate",c.three);
}
}).catch(function (error) {
console.log(error);
})

I'm assuming you're using Mongoose here, update() is an asynchronous function, your code is written in a synchronous style.
Try:
socket.on("input",function(d){
Promise.props({
one: collection2.aggregate([
{
$match:{post_id:mongoose.Types.ObjectId(d.id)}
},
{
$group:{
_id:"$post_id",
avg:{$avg:"$rating"}
}
}
]).exec()
}).then(function(a){
return collection3.update({_id:a.one[0]._id},{$set:{avg:a.one[0].avg}})
.then(function(updatedDoc){
// if update is successful, this function will execute
}, function(err){
// if an error occured, this function will execute
})
}).catch(function (error) {
console.log(error);
})

Mongoose docs says
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).
Also
Mongoose Update returns the updated document.
So this should look something like this.
function runBarryRun(d) {
Promise.props({
one: aggregateCollection2(d)
})
.then(updateCollection3)
.then(updatedDoc => {
// if update is successful, do some magic here
io.sockets.in('index|1').emit("estimate", updatedDoc.something);
}, err => {
// if update is unsuccessful, find out why, throw an error maybe
}).catch(function(error) {
// do something here
console.log(error);
});
}
function aggregateCollection2(d) {
return collection2.aggregate([{
$match: { post_id: mongoose.Types.ObjectId(d.id) }
}, {
$group: {
_id: "$post_id",
avg: { $avg: "$rating" }
}
}]).exec();
}
function updateCollection3(a) {
return collection3.update({ _id: a.one[0]._id }, { $set: { avg: a.one[0].avg } }).exec();
}
socket.on("input", runBarryRun);

Related

edit the last inserted record in mongodb

I am inserting two different objects into the db, i am doing this according to a certain criteria.
After that i am editing this record and setting the status to verified or not verified according to an amazon reply.
The problem is , i want to update the record that has been just inserted , since i am using findOneAndUpdate, only one record is being edited and it is not the last one it is the first.
Since the user can do as many purchases as he wants , he can have as many records as he want but only the first object found in the db having the userId sent as a param is edited.
what shall i use? the date and time when the object is inserted or what ?
async createAndSendToAmazon(data) {
try {
const records = new this.model(data);
const purchaseFromAppObjectRecord = await records.save();
let userId = purchaseFromAppObjectRecord.UserData[0].userId;
let receiptId = purchaseFromAppObjectRecord.receiptId;
await sendToAmazon(userId, receiptId);
await changeStatusToVerified(userId);
return purchaseFromAppObjectRecord;
} catch (error) {
return error;
}
}
}
async function sendToAmazon(userId, receiptId) {
const requestUrl = `https://appstore-sdk.amazon.com/version/1.0/verifyReceiptId/developer/2:smXBjZkWCxDMSBvQ8HBGsUS1PK3jvVc8tuTjLNfPHfYAga6WaDzXJPoWpfemXaHg:iEzHzPjJ-XwRdZ4b4e7Hxw==/user/${userId}/receiptId/${receiptId}`;
console.log(requestUrl);
fetch(requestUrl).then(function (response) {
if (response.status === 200) {
console.log(response.status);
response.json().then(async function (data) {
AmazonResolver.create(data);
});
} else {
try {
changeStatusToNotVerified(userId);
console.log(response.status);
response.json();
console.log("err will not add amazon verification object");
} catch (err) {
console.log(err);
}
}
});
}
async function changeStatusToVerified(userId) {
try {
await purchaseFromAppObjectModel.findOneAndUpdate(
{
UserData: { $elemMatch: { userId: userId } },
},
{ $set: { status: "verified" } }
);
} catch (err) {
console.log(err);
}
}
I want to write down my question as a minimal one but i want you to see my functions.
// you can use sort aggregate function to sort users in desc order and update the last element first
async function changeStatusToVerified(userId) {
try {
await purchaseFromAppObjectModel.findOneAndUpdate(
{
UserData: { $elemMatch: { userId: userId } },
},
{ $set: { status: "verified" } },
{ sort: { userId: -1 }, upsert: true, returnNewDocument: true }
);
} catch (err) {
console.log(err);
}
}
OR
async function changeStatusToVerified(userId) {
try {
await purchaseFromAppObjectModel.findOneAndUpdate(
{
UserData: { $elemMatch: { userId: userId } },
},
{ $set: { status: "verified" } },
{ sort: { userId: -1 } }
);
} catch (err) {
console.log(err);
}
}
if any one passes by here later on , this worked for me :
.findOneAndUpdate(
{
UserData: { $elemMatch: { userId: userId } },
},
{ $set: { status: "verified" }, limit: 1 }
)
.sort({ $natural: -1 });

Async function returns undefined even though values are returned

I have the following two functions one calling the other but the record variable is undefined followed by errors. I can't figure out why the script doesn't wait. It seems to just proceed with the undefined variable.
async function searchRecord(recordID) {
client.search({
index: 'records',
type: 'record',
body: {
query: { match: { _id: recordID } }
}
}).then(result => {
return result
}).catch(error => {
console.log(error)
return []
})
}
function test(jsonRecord) {
const userID = jsonRecord.users[0]
searchRecord(jsonRecord.objectID).then(record => {
if (record.length === 0) {
record = jsonRecord
}
})
}
The error that I get is:
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'length' of undefined
This is asynchronous, try using await.
async function searchRecord(recordID) {
try {
const result = await client.search({
index: 'records',
type: 'record',
body: {
query: {
match: { _id: recordID }
}
}
});
return result;
} catch (error) {
console.log(error);
return [];
}
}
Try updating searchRecord to return:
async function searchRecord(recordID) {
return client
.search({
index: "records",
type: "record",
body: {
query: {
match: { _id: recordID },
},
},
})
.then((result) => {
return result;
})
.catch((error) => {
console.log(error);
return [];
});
}
The function client.search() returns a promise. You could choose to return that promise as is from searchRecord(). And then, handle the catch in your test() function.
Alternatively you could also handle the error inside searchRecord() as well by implementing a try catch block. But the key in this case is to wait for client.search() to finish before returning from searchRecord().
function searchRecord(recordID) {
return client.search({
index: 'records',
type: 'record',
body: {
query: { match: { _id: recordID } }
}
});
}
function test(jsonRecord) {
const userID = jsonRecord.users[0]
searchRecord(jsonRecord.objectID).then(record => {
if (record.length === 0) {
record = jsonRecord
}
}).catch(error => {
console.log(error)
return []
})
}
The error that I get is: UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'length' of undefined
The reason for this is that searchRecord() returns promise that immediately resolves to undefined. There is no return statement in the function searchRecord().
why you not using Promise? it's ok if you want to use async-await like answers above but using Promise its became very easy
function searchRecord (recordID) {
return new Promise((resolve, reject)=>{
client.search({
index: 'records',
type: 'record',
body: {
query: {
match: { _id: recordID }
}
}
}).then(
result => resolve(result)
).catch(
error => {console.log(error);reject());
});
}
function test (jsonRecord) {
const userID = jsonRecord.users[0]
searchRecord(jsonRecord.objectID)
.then(
record => {
if (record.length === 0) {
record = jsonRecord
}
}
)
}

How to architect array of promises in GraphQL resolver with multiple API calls to return a single object type list

I'm stuck in my GraphQL resolver fetching todo-lists for a particular user belonging to a company. According to whether or not they have access to all todo-lists or a certain few, it will fetch for groups the user registered to that have belonging todo-lists, and those should be fetched.
The code so far is capable of logging the requested todo-lists on the query but I have yet to come to the solution on how to actually return data of all of the user's registered groups's todo-lists.
I chose to export the actual logic into a separate function
The Resolver:
allowedListItems: {
type: new GraphQLList(TodoItem),
resolve(parentValue, args) {
return Promise.all([fetchAllowedItems(parentValue._id)]);
}
},
The Promise Function
function fetchAllowedItems(userId) {
return User.findOne({ _id: userId }).then((user) => {
if (user.todoGroups) {
return user.todoGroups.map((groupId) => {
return TodoGroup.findOne({ _id: groupId }).then(group => {
return group.todoLists.map((listId) => {
return TodoList.findOne({ _id: listId })
})
})
})
} else {
return TodoList.find({ company: parentValue.company }).exec();
}
})
}
I am not getting any errors from GraphQL so I guess it's about the way I make the promisses return to the resolver, I'd appreciate a lot if you can help me out!
Update:
I should wrap the maps with a Promise.all, as the mapping returns an array.
Though the updated code brings no improvement in the returned data.
async resolve(parentValue, args) {
let user = await User.findOne({ _id: parentValue._id })
if (user.todoGroups) {
return Promise.all(user.todoGroups.map((groupId) => {
return TodoGroup.findOne({ _id: groupId }).then(group => {
return Promise.all(group.todoLists.map((listId) => {
return TodoList.findOne({ _id: listId });
}))
})
}))
} else {
return TodoList.find({ company: parentValue.company }).exec();
}
}
},
Current query result:
{
"data": {
"user": {
"_id": "5ba11690ad7a93d2b34d21a9",
"allowedTodos": [
{
"_id": null,
"title": null
}
]
}
}
}
You need to call Promise.all on an array of promises, not a promise for that. Also you'll have to call it on each level:
allowedListItems: {
type: new GraphQLList(TodoItem),
resolve(parentValue, args) {
return User.findOne({ _id: parentValue._id }).then(user => {
if (user.todoGroups) {
return Promise.all(user.todoGroups.map(groupId => {
// ^^^^^^^^^^^^
return TodoGroup.findOne({ _id: groupId }).then(group => {
return Promise.all(group.todoLists.map(listId => {
// ^^^^^^^^^^^^
return TodoList.findOne({ _id: listId })
}));
});
}));
} else {
return TodoList.find({ company: parentValue.company }).exec();
}
});
}
}

Using async.js for deep populating sails.js

I have a big issue with my function in sails.js (v12). I'm trying to get all userDetail using async (v2.3) for deep populating my user info:
UserController.js:
userDetail: function (req, res) {
var currentUserID = authToken.getUserIDFromToken(req);
async.auto({
//Find the User
user: function (cb) {
User
.findOne({ id: req.params.id })
.populate('userFollowing')
.populate('userFollower')
.populate('trips', { sort: 'createdAt DESC' })
.exec(function (err, foundedUser) {
if (err) {
return res.negotiate(err);
}
if (!foundedUser) {
return res.badRequest();
}
// console.log('foundedUser :', foundedUser);
cb(null, foundedUser);
});
},
//Find me
me: function (cb) {
User
.findOne({ id: currentUserID })
.populate('myLikedTrips')
.populate('userFollowing')
.exec(function (err, user) {
var likedTripIDs = _.pluck(user.myLikedTrips, 'id');
var followingUserIDs = _.pluck(user.userFollowing, 'id');
cb(null, { likedTripIDs, followingUserIDs });
});
},
populatedTrip: ['user', function (results, cb) {
Trip.find({ id: _.pluck(results.user.trips, 'id') })
.populate('comments')
.populate('likes')
.exec(function (err, tripsResults) {
if (err) {
return res.negotiate(err);
}
if (!tripsResults) {
return res.badRequest();
}
cb(null, _.indexBy(tripsResults, 'id'));
});
}],
isLiked: ['populatedTrip', 'me', 'user', function (results, cb) {
var me = results.me;
async.map(results.user.trips, function (trip, callback) {
trip = results.populatedTrip[trip.id];
if (_.contains(me.likedTripIDs, trip.id)) {
trip.hasLiked = true;
} else {
trip.hasLiked = false;
}
callback(null, trip);
}, function (err, isLikedTrip) {
if (err) {
return res.negotiate(err);
}
cb(null, isLikedTrip);
});
}]
},
function finish(err, data) {
if (err) {
console.log('err = ', err);
return res.serverError(err);
}
var userFinal = data.user;
//userFinal.trips = data.isLiked;
userFinal.trips = "test";
return res.json(userFinal);
}
);
},
I tried almost everthing to get this fix but nothing is working...
I am able to get my array of trips(data.isLiked) but I couldn't get my userFInal trips.
I try to set string value on the userFinal.trips:
JSON response
{
"trips": [], // <-- my pb is here !!
"userFollower": [
{
"user": "5777fce1eeef472a1d69bafb",
"follower": "57e44a8997974abc646b29ca",
"id": "57efa5cf605b94666aca0f11"
}
],
"userFollowing": [
{
"user": "57e44a8997974abc646b29ca",
"follower": "5777fce1eeef472a1d69bafb",
"id": "5882099b9c0c9543706d74f6"
}
],
"email": "test2#test.com",
"userName": "dany",
"isPrivate": false,
"bio": "Hello",
"id": "5777fce1eeef472a1d69bafb"
}
Question
How should I do to get my array of trips (isLiked) paste to my user trips array?
Why my results is not what I'm expecting to have?
Thank you for your answers.
Use .toJSON() before overwriting any association in model.
Otherwise default toJSON implementation overrides any changes made to model associated data.
var userFinal = data.user.toJSON(); // Use of toJSON
userFinal.trips = data.isLiked;
return res.json(userFinal);
On another note, use JS .map or _.map in place of async.map as there is not asynchronous operation in inside function. Otherwise you may face RangeError: Maximum call stack size exceeded issue.
Also, it might be better to return any response from final callback only. (Remove res.negotiate, res.badRequest from async.auto's first argument). It allows to make response method terminal

how to populate() a mongoose .findOneAndUpdate object

The code below works, it updates a record or creates one if it doesn't exist yet. However, I'd like to combine this findOneAndUpdate() statement with the populate() method in order to populate the "user" of my object. What would be the right way to add the populate("user") statement to this logic?
I tried adding the populate() method after the findOneAndUpdate finishes but that returns an error saying that this method doesn't exist. I'm running the latest version of mongoose.
LoyaltyCard.findOneAndUpdate({ business: businessid}, { $set: newCard, $inc: { stamps: +1 } }, { upsert: true}, function(err, card){
if(err)
{
}
else
{
}
res.json(result);
});
Use exec() instead of a callback parameter:
LoyaltyCard.findOneAndUpdate(
{business: businessid},
{$set: newCard, $inc: {stamps: +1}},
{upsert: true}
)
.populate('user')
.exec(function(err, card) {
if (err) {
// ...
} else {
res.json(result);
}
});
With async/await I removed the exec
const getLoyaltyCard = async () => {
const results = await LoyaltyCard.findOneAndUpdate(
{ business: businessid },
{ $set: newCard, $inc: { stamps: + 1 } },
{ upsert: true }
)
.populate('user')
return results
}
You can also add a populate object in the 3rd parameter of .findOneAndUpdate() as one of the option, like this:
LoyaltyCard.findOneAndUpdate(
{ business: businessid },
{ $set: newCard, $inc: { stamps: +1 } },
{ upsert: true, populate: { path: 'user' } }
)
.exec(function(err, card) {
if (err) {
// ...
} else {
res.json(result);
}
});
Just enhancing #rahulchouhan's answer:
You can add the populate as one of the options which is the third parameter of findOneAndUpdate function and it works just like any other promise (then, catch)
LoyaltyCard.findOneAndUpdate(
{ business: businessid },
{ $set: newCard, $inc: { stamps: +1 } },
{ upsert: true, populate: { path: 'user' } }
)
.then(card => {
res.status(200).json(card);
}).catch(err => {
res.status(500).json({ message: err.message});
});

Categories

Resources