I use sails.js to update stock data from difference variable. When I do, console.log(product.stock), the value is 4. But it seems product.save() function below is not executed because attribute stock still not changed to 4. I guess the problem is at promise. Anybody know how to fix this?
exports.updateProductStock = function (details) {
details.forEach(function( detail ) {
var findPrevDetailRule = {
invoice: detail.invoice,
product: detail.product.id
};
var createPrevDetailRule = {
invoice: detail.invoice,
product: detail.product.id,
quantity: detail.quantity
};
var requirementBeforeUpdateStock = [
PreviousDetail.findOne(findPrevDetailRule).sort('createdAt desc').then(),
PreviousDetail.create(createPrevDetailRule).then()
];
Q.all(requirementBeforeUpdateStock)
.spread(function( prevDetail, newPrevDetail ) {
var difference = detail.quantity - prevDetail.quantity;
return {
prevDetail: prevDetail,
difference: difference
};
})
.then(function( results ) {
Product.findOne(results.prevDetail.product).then(function(product) {
product.stock += results.difference;
// maybe this below is not execute
product.save();
console.log(product.stock);
});
})
});
Note: I use sails 0.10-rc8. Have tested with sails-mysql, sails-mongo, sails-postgres, but still same.
.save() accepts a callback with which you can report success/failure.
For example, you can repost back to the client (from the sails documentation) :
product.save(function (err) {
if (err) return res.send(err, 500);
res.json(product);
});
Depending on scope, you may need to pass res into your function
Alternatively, monitor success/failure in the console, eg ,
product.save(function (err) {
if (err) console.log('save error: ' + err);//assuming err to be a string
console.log('save success');
});
Should at least give you a clue as to what's going wrong.
Related
I'm trying to get user lang from User class in Parse Cloud. lang is one of the columns in User class. I wanna get lang of the user. My entire Cloud Code is as following (it didn't work):
Parse.Cloud.beforeSave('Order', function(request, response) {
var orderState = request.object.get('orderState');
var subtitleMessage = '';
var userLang = '';
var currentUser = request.object.get('user');
var userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('objectId', currentUser.id);
.find()
.then((result)=>{
userLang = result.get('lang');
})
if (orderState === undefined || ['nonApproved', 'approved', 'delivered', 'canceled'].indexOf(orderState) < 0) {
response.error("OrderState is null or not one the ['nonApproved', 'approved', 'delivered', 'canceled']!");
} else {
var query = new Parse.Query(Parse.Installation);
query.include('user');
query.equalTo('user', request.object.get('user'));
Parse.Push.send(
{
where: query,
data: {
title: "MyTitle",
alert: subtitleMessage
}
},
{
success: function() {
},
error: function(error) {
response.error(error)
},
useMasterKey: true
}
);
response.success();
}
});
The answer from Jake T. has some good points. Based on his answer and your comment on the question, you could try something like this:
Parse.Cloud.beforeSave('Order', function(request, response) {
var currentUser = request.object.get('user');
currentUser.fetch({useMasterKey: true}).then(function(user) {
var userLang = user.get('lang');
// send push notifications based on userLang here
response.success();
}).catch(function(error) {
// handle any errors here
console.error(error);
});
});
Verify you actually have a User object shell from request.object.get("user"); And, if you do, you can just call currentUser.fetch() instead of performing a query, unless there are other objects you may need to include.
Since you used a query, the result is an array, even if there is only a single object returned (or none, it would be simply []). So, you're doing Array.get("lang"), which shouldn't do anything. Try if( results && results.length > 0 ) user = results[0];, then you should be able to access user.get("lang");
You should have ACL / CLP's set up for your User class. Ideally, these should not be accessible by people who are not the user or master. So, if that is set up properly, your solution may be passing {useMasterKey:true} as an option to the query / fetch.
I've been working on this app for a while. I have several other modules that all work fine. I've been having a ton of trouble with this particular module and it's super frustrating. This problem looks super simple. Maybe I'm over thinking it. Hopefully someone will say that I am. :)
In this module, I decided to use methods from my model. This particular one is non-instanced. Here is my model:
/*
* Account.js
*/
module.exports = {
connection: 'islMongo',
attributes: {
name: {
type: 'string',
required: true,
},
},
numberToName: function(accountNumber) {
Account.findOne(accountNumber).exec(function(err, a){
if (err) {
return 'err';
} else {
return 'ok';
}
});
return 'broke';
},
};
I call it from one of my controllers like this:
var accountName = Account.numberToName(params.id);
At this point accountName's value is "broke". I don't understand why it wouldn't either return "err" or "ok". I simplified my actual function here for testing.
Edit:
I have other calls that work properly. For instance:
updateBalance: function(account, amount, callback) {
/* Accepts account id or account object */
(function _lookupAccount(afterLookup) {
if (typeof account === 'object') return afterLookup(null, account);
Account.findOne(account)
.exec(afterLookup);
})(function (err, a) {
if (err) return callback(err);
if (!a) {
err = new Error();
err.message = "Couldn't find account.";
err.status = 400;
return callback(err);
}
a.balance = parseInt(a.balance) + parseInt(amount);
a.save(callback);
});
},
Is called like this:
Account.updateBalance(params.account, -2000);
The definition has a callback, but I don't actually use one because it isn't needed. The method works fine.
Sails.js documentation provides example methods that don't use callbacks. They simply return the requested data.
// Attribute methods
getFullName: function (){
return this.firstName + ' ' + this.lastName;
},
isMarried: function () {
return !!this.spouse;
},
isEligibleForSocialSecurity: function (){
return this.age >= 65;
},
encryptPassword: function () {
}
And called like this:
if ( rick.isMarried() ) {
// ...
}
Which is what I am trying to do with my method at the top of this post. It seems like the exec() portion of Account.findOne() isn't even being called.
Sails.js & Node.js are asynchronous. So in simple words they don't wait for response from database, but when they got date they call a callback. So you need to read about Queries and callbacks and what is callback hell (you should never do that).
And now get back to your problem.
/*
Account.js
*/
//...
numberToName: function(accountNumber, callback) {
// if you want some additional logic you can create function here and call callback in it
Account.findOne(accountNumber).exec(callback);
}
//...
Tip: callbacks first param is always error.
// AccountController
method: function(req, res){
var id = req.param('id'); // if its int you should parseInt()
var callback = function(error, account){
if(error)
res.send('error');
else
res.send(account.name);
};
Account.numberToName(id, callback);
}
I'm using Parse to represent the state of a beer keg (among other things). I'd like to check the user's notifications, stored in a "Notifications" table, to see if they'd like to receive a notification when the keg is filled.
I have all of the logic for setting the user's notification settings as well as sending notifications in cloud/notifications.js. All of the logic for updating the keg is in cloud/beer.js. I created an exported function called "sendKegRefillNotification" which performs a query.find() on the Notifications table and gets called from beer.js.
The problem is that it doesn't seem to be executing query.find() when I call the function from beer.js, however when I call the same function from a job within notifications.js, it works just fine.
main.js:
require("cloud/beer.js");
require("cloud/notifications.js");
beer.js:
var notify = require("cloud/notifications.js");
var Keg = Parse.Object.extend("Keg");
var fillKeg = function(beerName) {
var promise = new Parse.Promise();
var keg = new Keg();
keg.set("beerName", beerName)
keg.set("kickedReports", []);
keg.save(null, { useMasterKey: true }).then(function(keg) {
console.log("Keg updated to " + beerName + ".");
promise.resolve(keg);
notify.sendKegRefillNotification(keg);
},
function(keg, error) {
promise.reject(error);
});
return promise;
}
Parse.Cloud.define("beerFillKeg", function(request, response) {
var beerName = request.params.name;
if (!beerName) {
response.error("No beer was specified.");
return;
}
if (!util.isUserAdmin(request.user)) {
response.error("User does not have permission to update the keg.");
return;
}
fillKeg(beerName).then(function(keg) {
kegResponse(keg).then(function(result) {
response.success(result);
});
},
function(error) {
response.error(error);
});
});
function kegResponse(keg) {
var promise = new Parse.Promise();
var result = {
id: keg.id,
beer: {
name: keg.get("beerName")
},
filled: keg.createdAt,
kickedReports: []
};
var kickedReports = keg.get("kickedReports");
if (!kickedReports || kickedReports.length == 0) {
promise.resolve(result);
} else {
util.findUsers(kickedReports).then(function(users) {
result.kickedReports = util.infoForUsers(users);
promise.resolve(result);
}, function(users, error) {
console.log(error);
promise.resolve(result);
});
}
return promise;
}
notifications.js:
var Keg = Parse.Object.extend("Keg");
var Notification = Parse.Object.extend("Notifications");
exports.sendKegRefillNotification = function(keg) {
var beerName = keg.get("beerName");
console.log("Sending notifications that keg is refilled to '" + beerName + "'.");
var promise = new Parse.Promise();
var query = new Parse.Query(Notification);
query.include("user");
query.equalTo("keg_filled", true);
query.find({ useMasterKey: true }).then(function(notifications) {
console.log("Found notifications!");
promise.resolve("Found notifications!");
},
function(notifications, error) {
console.error("No notifications");
console.error(error);
promise.reject(error);
});
return promise;
}
Parse.Cloud.job("beerSendRefillNotification", function(request, status) {
var query = new Parse.Query(Keg);
query.descending("createdAt");
query.first().then(function(keg) {
if (!keg) {
status.error("No keg");
return;
}
exports.sendKegRefillNotification(keg);
},
function(keg, error) {
response.error(error);
});
});
When I run the job "beerSendRefillNotification" from the Parse dashboard, I can tell that query.find() is getting called because it prints "Found notifications!":
E2015-02-23T06:59:49.006Z]v1564 Ran job beerSendRefillNotification with:
Input: {}
Result: success/error was not called
I2015-02-23T06:59:49.055Z]false
I2015-02-23T06:59:49.190Z]Sending notifications that keg is refilled to 'test'.
I2015-02-23T06:59:49.243Z]Found notifications!
However, when I call the cloud function "beerFillKeg", it isn't because it's not printing "Found notifications!" or "No notifications":
I2015-02-23T07:00:17.414Z]v1564 Ran cloud function beerFillKeg for user HKePOEWZvC with:
Input: {"name":"Duff"}
Result: {"beer":{"name":"Duff"},"filled":{"__type":"Date","iso":"2015-02-23T07:00:17.485Z"},"id":"olLXh0F54E","kickedReports":[]}
I2015-02-23T07:00:17.438Z]false
I2015-02-23T07:00:17.523Z]Keg updated to Duff.
I2015-02-23T07:00:17.525Z]Sending notifications that keg is refilled to 'Duff'.
I finally understand it. In sendKegRefillNotification, you're calling query.find({...}), then returning an object. That find is asynchronous, and you're doing nothing to wait for the result. I think you need to return the find function call, rather than an object you set within that method.
In other words, you're running along, leaving some async running code behind you.
Edit: I understand what you tried to do. It sort of makes sense. You defined a promise, and thought the caller would wait for the promise. The problem is, the promise is defined in an asynchronous block. It doesn't yet have any meaning at the moment the caller gets it.
It looks like Parse doesn't allow you to run a query from inside a callback from save(). When I moved "notify.sendKegRefillNotification(keg);" to outside of the callback, it worked.
var fillKeg = function(beerName) {
var promise = new Parse.Promise();
var keg = new Keg();
keg.set("beerName", beerName)
keg.set("kickedReports", []);
keg.save(null, { useMasterKey: true }).then(function(keg) {
console.log("Keg updated to " + beerName + ".");
console.log("Send notifications.");
promise.resolve(keg);
},
function(keg, error) {
promise.reject(error);
});
notify.sendKegRefillNotification(keg); // Now this works
return promise;
}
Can anyone shed some more light on why this worked?
I am trying to create a simple Cloud Job on parse.com but it doesn't behave as expected.
The job returns without error but in the process I am making a find query that seems to be thrown out to the void. There is no error, my console.log are visible before executing query.find() but after that nothing... The query seems to fail silently.
Here is my code:
Parse.Cloud.job("maintenanceJob", function(request, status) {
return performMaintenanceTasks().then(function() {
status.success("Parse Job done");
}, function(errors) {
status.error(tools.prettifyError(errors));
});
});
function performMaintenanceTasks ()
{
// If we have more than NB_MAX_ITEMS objects in Items, let's delete some
var query = new Parse.Query(Items);
return query.count({
success: function(count) {
if(count > NB_MAX_ITEMS) {
return deleteOldItems(1); // 1 is used for test
}
return Parse.Promise.as("Nothing to do.");
},
error: function(error) {
return Parse.Promise.error(error);
}
});
}
function deleteOldItems(nbToDelete) {
// (...)
var query = new Parse.Query(Items);
query.ascending("createdAt");
query.limit(nbToDelete);
query.include("rawData");
console.log("I am visible in console, but NOTHING AFTER ME. query.find() seems to return immediately");
return query.find({
success: function (results) {
// I never pass here
var promise = Parse.Promise.as();
_.each(results, function (item) {
// For each item, extend the promise with a function to delete it.
promise = promise.then(function () {
var rawData = item.get("rawData");
// If we have a rawData, delete it before Item
if (rawData && rawData.id) {
return rawData.destroy({
success: function (theObj) {
return item.destroy({
success: function (anotherObj) {
// I never pass here
return Parse.Promise.as();
},
error: function (anotherObj, error) {
// I never pass here
return Parse.Promise.as();
}
});
},
error: function (theObj, error) {
// I never pass here
return Parse.Promise.as();
}
});
} else {
return item.destroy({
success: function (anotherObj) {
// I never pass here
return Parse.Promise.as();
},
error: function (anotherObj, error) {
// I never pass here
return Parse.Promise.as();
}
});
}
});
});
return promise;
},
error: function (error) {
// I never pass here
return Parse.Promise.error(error);
}
}).then(function (nil) {
// I never pass here
return Parse.Promise.as("DELETEOLDITEMS: Job finished");
}, function(error) {
// I never pass here
return Parse.Promise.error(error);
});
}
(I have tested to replace every // I never pass here with console.log(), without any result)
I tried many different things but I believe this should work! Or at least return errors!
Anyone know what I am doing wrong? Thanks in advance!
EDIT:
Even weirder, if I modify performMaintenanceTasks to skip query.count():
function performMaintenanceTasks()
{
return deleteOldItems(1);
}
the query.find() in deleteOldItems() is correctly executed this time!
What does that mean? Am I not allowed to nest queries on the same class?
I'm not certain if this pertains to you, but I know from my a personal experience that the Parse log can seem a little unintuitive. The Parse log only spits out 10 lines by default, so ensure you're specifying the log length every time you check.
parse log -n 1000
...is what I tend to do every time. This just makes debugging easier.
I'm trying to fill some local data resolving a series of remote calls.
When every promise is resolved, I load the data and proceed.
The method $q.all( [] ) does exactly this:
$q.all([
this.getUserInfo(11)
.then(function (r) {
results.push(r)
}),
this.getUserConns()
.then(function (r) {
results.push(r)
}),
this.getUserCtxs()
.then(function (r) {
results.push(r)
})
])
.then(function () {
console.log(results)
})
Problem is, this code is not resilient.
If any of these call fails, nobody gets the fish!
Wrapping the calls in a try/catch statement, simply causes $q.all() to entirely ignore the entry, even when not failing (note the console.log in the func)...
$q.all([
this.getUserInfo2(11)
.then(function (r) {
results.push(r)
}),
function () {
try {
this.getUserGroups()
.then(function (r) {
console.log(r)
results.push(r)
})
}
catch (err) {
console.log(err)
}
},
])
.then(function () {
console.log(results)
})
Output:
[Object]
Any hint on how I could wrap this to be resilient?
Thanks to #dtabuenc, I've gone one step further.
Implementing the error callback, I can avoid the breaking of the chain, and push the values of the resolved promises.
However, a nasty Exception is still displayed on the console...
How can I get rid of that if I cannot try/catch on async requests?
Caller code
return $q.all([
this.getUserInfo(user_id)
.then(function (r) {
results['personal_details'] = r
}),
this.getUserConns()
.then(
function (r) {
results['connections'] = r
},
function(err) {
console.log(err)
})
])
.then(function () {
return (results)
})
Callee code (inject with an exception)
getUserConns: function() {
return __doCall( ws.getUserConnections, {} )
.then( function(r) {
// very generic exception injected
throw new Error
if (r && r.data['return_code'] === 0) {
return r.data['entries']
}
else {
console.log('unable to retrieve the activity - err: '+r.data['return_code'])
return null
}
})
},
This will work but also push the errors to the array.
function push(r) {
results.push(r);
}
$q.all([
this.getUserInfo(11).then(push).catch(push),
this.getUserConns().then(push).catch(push),
this.getUserCtxs().then(push).catch(push)
])
.then(function () {
console.log(results);
})
You should also improve your understanding of promises, you never should use try-catch with promises - when using promises, you use the .catch() method (with everything else being implicitly a try). This works for normal errors as well as asynchronous errors.
If you want to totally ignore the errors:
function push(r) {
results.push(r);
}
function noop() {}
$q.all([
this.getUserInfo(11).then(push).catch(noop),
this.getUserConns().then(push).catch(noop),
this.getUserCtxs().then(push).catch(noop)
])
.then(function () {
console.log(results);
})
I think it's easier to do :
$q.all([
mypromise1.$promise.catch(angular.noop),
mypromise2.$promise.catch(angular.noop),
mypromise1.$promise.catch(angular.noop)
])
.then(function success(data) {
//.....
});
I'm not sure what you mean by resilient. What do you want to happen if one of the promises fails?
Your try-catch won't work because the promise will fail asynchronously.
You can however pass in an error handler as the second parameter to the then() call and do whatever you wish there.
Same issue here. For those of you with for loops: inside a then response:
var tracks = [];
var trackDfds = [];
for(var i = 0; i < res.items.length; i++){
var fn = function () {
var promise = API.tracks(userId, res.items[i].id);
return promise.then(function (res) {
if (res.items.length) {
tracks.push(res.items);
}
}).catch(angular.noop);
};
trackDfds.push(fn());
}
$q.all(trackDfds)
.then(function (res) {
console.log(tracks);
});
#Esailija's answer seems like a workaround to a problem.
You can't resolve the problem outside the main contributor to the problem: $q.
It seems a bit wiser to have reject callbacks for each then (2nd argument) and in there to insert $q.reject(...).
Example:
$q.all([
this.getUserInfo(11).then(
function (response) { // UI data preparation for this part of the screen },
function (response) {
$q.reject(response);
}
),
// ...
])
.then(
function () {
// all good
},
function () {
// at least one failed
}
)
This is particularly indicated when the UI model depends on all ajax calls.
Personally I think this is the safe way to proceed anyway, because most of the times you do want to push some server messages to some toast component on the reject callbacks, or alert the user in some way (queuing 7 ajax calls doesn't mean you can't show anything because 1 failed - it means you won't be able to show some region of the screen - that needs a specialized feedback to the user).