Parse Cloud: Query not running in exported function from save() callback - parse-platform

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?

Related

how to make two queries atomic in parse.com in beforeSave and afterSave triggers

I made two classes in parse User(by default) and UserData.
BeforeSave Trigger as follows:
Parse.Cloud.beforeSave(Parse.User, function(request, response) {
var userDataObject = new Parse.Object("UserData");
var fromUserPointer = {"__type":"Pointer","className":"_User","objectId":request.object.id};
return userDataObject.save({score: 0, ideasCount: 0, followersCount:0, return:0}).then(function (userData) {
var userDataPointer = {"__type":"Pointer","className":"UserData","objectId":userData.id};
request.object.set("userData", userDataPointer);
response.success();
}, function(error) {
response.error(error.message);
});
});
It saves user's UserData and takes its UserData pointer field and saves in UserData class.
Parse.Cloud.afterSave(Parse.User, function(request) {
Parse.Cloud.useMasterKey();
var userPointer = {"__type":"Pointer","className":"_User","objectId":request.object.id};
var userData = request.object.get("userData");
if (userData) {
var userDataPointer = {"__type":"Pointer","className":"UserData","objectId":userData.id};
var userDataQuery = new Parse.Query("UserData");
userData.set("user", userPointer);
return userData.save().then (function (userData) {
var activityObject = new Parse.Object("Activity");
return activityObject.save({fromUserData: userDataPointer, from:userPointer,
toUserData:userDataPointer, to:userPointer, type:"follow"});
}).then (function (success) {
}, function (error) {
console.error("Error in afterSave(user) : " + request.object.id + ":" + error.message);
});
}
});
The problem is that, before saving in the User class, it sometimes does not create UserData, which it should create. Hence these two triggers must run atomically, which they do not.

Got promise not working

I'm trying to use promise to get in promise2
But if I have an object Widgets with several elements in it...
Why can't I have been able to get my console.log's output
Parse.Cloud.define("extract", function(request, response) {
var user = request.params.user;
var promise = Parse.Promise.as();
[...]
}).then(function() {
return query.find().then(function(results) {
_.each(results, function(result) {
[...]
Widget.objectId = result.id;
Widgets[timestamp] = Widget;
});
return promise;
}).then(function(results) {
for (var key in Widgets) {
var Widget = Widgets[key];
var widget_data = Widgets[key].widget_data;
var promise2 = Parse.Promise.as();
promise2 = promise2.then(function() {
return Parse.Cloud.run('extractWidgetData', {
'widget_data': widget_data,
}).then(function(newresult) {
Widgets[key].data = newresult.data;
console.log('--------WHY NOT HERE ALL TIME ?--------');
});
});
return promise2;
}
}).then(function() {
response.success(Widgets);
},
function(error) {
response.error("Error: " + error.code + " " + error.message);
});
});
});
I'm becoming crazy to run this damn Code
EDIT : I finally followed Roamer's advices to implement something but I'm not sure if it's the good way to work with Promise in series...
Parse.Cloud.define("extract", function(request, response) {
var user = request.params.user;
var Widgets = {};
...
... .then(function() {
return query.find().then(function(results) {
return Parse.Promise.when(results.map(function(result) {
var Widget = ...;//some transform of `result`
Widget.id = ...;//some transform of `result`
var timestamp = createdAtDate.getTime();
...
return Parse.Cloud.run('extractData', {
'widget_data': Widget.widget_data,
}).then(function(newresult) {
Widget.stat = newresult.stats;
return Widget;//<<<<<<< important! This ensures that results.map() returns an array of promises, each of which delivers a Widget objects.
});
}));
}).then(function() {
var promisedWidget = Array.prototype.slice.apply(arguments);
return Parse.Promise.when(promisedWidget.map(function(Widget) {
return Parse.Cloud.run('getWineStats', {
'id': Widget.data.id
}).then(function(stat) {
Widget.stat = stat;
return Widget;
});
}));
}).then(function() {
var promisedWidget = Array.prototype.slice.apply(arguments);
_.each(promisedWidget, function(Widget) {
var createdAtObject = Widget.createdAt;
var strDate = createdAtObject.toString();
var createdAtDate = new Date(strDate);
timestamp = createdAtDate.getTime();
Widgets[timestamp] = Widget;
});
return Widgets;
}).then(function(Widgets) {
response.success(Widgets);
},
function(error) {
response.error("Error: " + error.code + " " + error.message);
});
});
});
First, I echo Bergi's comment on indentation/matching parenthesis.
But ignoring that for a moment, at the heart of the code you have return query.find().then(...).then(...).then(...) but the flow from the first .then() to the second is incorrect. Besides which, only two .then()s are necessary as the code in the first then is synchronous, so can be merged with the second.
Delete the two lines above for (var key in Widgets) { then at least Widgets will be available to be processed further.
Going slightly further, you should be able to do all the required processing of results in a single loop. There seems to be little pont in building Widgets with _.each(...) then looping through the resulting object with for (var key in Widgets) {...}.
In the single loop, you probably want a Parse.Promise.when(results.map(...)) pattern, each turn of the map returning a promise of a Widget. This way, you are passing the required data down the promise chain rather than building a Widgets object in an outer scope.
Do all this and you will end up with something like this :
Parse.Cloud.define("extract", function(request, response) {
var user = request.params.user;
...
... .then(function() {
return query.find().then(function(results) {
return Parse.Promise.when(results.map(function(result) {
var Widget = ...;//some transform of `result`
...
return Parse.Cloud.run('extractWidgetData', {
'widget_data': Widget.widget_data,
}).then(function(newresult) {
Widget.data = newresult.data;
return Widget;//<<<<<<< important! This ensures that results.map() returns an array of promises, each of which delivers a Widget objects.
});
}));
}).then(function() {
//Here, compose the required Widgets array from this function's arguments
var Widgets = Array.prototype.slice.apply(arguments);//Yay, we got Widgets
response.success(Widgets);
}, function(error) {
response.error("Error: " + error.code + " " + error.message);
});
});
});

parse.com A Cloud Job of few words

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.

SailsJS how to correct this promise issue?

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.

AngularJS: Using $q to fire ajax calls synchronously

Is it possible to use $q to fire ajax requests synchronously in AngularJS?
I have a long list of vehicles, each vehicle has events associated with them and I need to retrieve the eventdetails of each event when the user expands the listing.
Right now, if the user expands the listing, I am firing up to 15 calls asynchronously and it seems to be causing issues with the API I'm consuming, so I'd like to see if performance is improved if I wait for each request finishes before firing the next.
I'm attempting to implement $q to delay the next request until the previous is finished, however I can't seem to wrap my head around using the service, here is what I currently have:
// On click on the event detail expander
$scope.grabEventDetails = function(dataReady, index) {
if (dataReady == false) {
retrieveEventDetails($scope.vehicles[index].events);
}
}
var retrieveEventDetails = function(events) {
// events is array
var deferred = $q.defer();
var promise = deferred.promise;
var retrieveData = function(data) {
return $http({
url: '/api/eventdetails',
method: 'POST',
data: {
event_number: data.number
},
isArray: true
});
}
_.each(events, function(single_event) {
promise.then(retrieveData(single_event).success(function(data) {
console.log(data);
}));
});
}
This is still firing asynchronously, Where am I going wrong with this?
I understand firing the requests synchronously isn't the best idea, at the moment I just want to see if performance is improved with the API at all.
You don't need $q to implement a promise as $http returns one.
_.each fires all the callbacks without especially waiting the promise.
All you do is call retrieveData for all events whenever your promise is resolved, and since you don't do a first call, it shouldn't even be working
You could do some recursive call like this :
var retrieveEventDetails = function(events) {
var evt = events.shift();
$http({
url: '/api/eventdetails',
method: 'POST',
data: {
event_number: evt.number
},
isArray: true
}).then(function(response){
console.log(response.data);
retrieveEventDetails(events);
});
}
I do think you should use $q as some other part of your application might need to get a promise.
A good example would be $routeProvider resolve option.
I made a little demo in plunker.
Solution:
retrieveData function should return a function (which returns a promise) instead of a just a promise.
That way we can create a promise chain: promise.then(fn).then(fn).then(fn).then(null,errorFn)
We must resolve the first promise to kick the chain.
var retrieveEventDetails = function(events) {
// events is array
var deferred = $q.defer();
var promise = deferred.promise;
var retrieveData = function(data) {
return function(){
return $http({
url: '/api/eventdetails',
method: 'POST',
data: {
event_number: data.number
},
isArray: true
})
}
}
deferred.resolve();
return events.reduce(function(promise, single_event){
return promise.then(retrieveData(single_event));
}, promise);
}
I'm not sure you even need $q here. In this example, each piece of data is registered in the controller as soon as it comes back from the call.
Live demo (click).
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
$scope.datas = myService.get();
});
app.factory('myService', function($http) {
var myService = {
get: function() {
var datas = {};
var i=0;
var length = 4;
makeCall(i, length, datas);
return datas;
}
}
function makeCall(i, length, datas) {
if (i < length) {
$http.get('test.text').then(function(resp) {
datas[i] = resp.data+i;
++i;
makeCall(i, length, datas);
});
}
}
return myService;
});
Here's a way using $q.all() that you can wait for all of the data to come through before passing it to the controller: Live demo (click).
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
myService.get().then(function(datas) {
$scope.datas = datas;
})
});
app.factory('myService', function($q, $http) {
var myService = {
get: function() {
var deferred = $q.defer();
var defs = [];
var promises = [];
var i=0;
var length = 4;
for(var j=0; j<length; ++j) {
defs[j] = $q.defer();
promises[j] = defs[j].promise;
}
makeCall(i, length, defs);
$q.all(promises).then(function(datas) {
deferred.resolve(datas);
});
return deferred.promise;
}
}
function makeCall(i, length, defs) {
if (i < length) {
$http.get('test.text').then(function(resp) {
defs[i].resolve(resp.data+i);
++i;
makeCall(i, length, defs);
})
}
}
return myService;
});

Resources