1

I am trying to clean my Parse Cloud Code functions to make them easier to maintain. To do so I tried to use Promises but I can't get rid of errors.

Here is the aim of my code :

  • Decrement score of User1
  • Push a notification to User2 saying --> User1.name is asking you to : Action

Actual Cloud Code (working) :

Parse.Cloud.useMasterKey();
var action = request.object.get("action");
var from = request.object.get("from");
var to = request.object.get("to");
var reward = request.object.get("reward");

// Query 'from User' to decrement his score
var queryScore = new Parse.Query(Parse.User);
queryScore.get(from.id, {
  success: function(score)
  {
    // Decrement score of 'fromUser'.
    var newScore = score.get("score");
    newScore -= reward;
    score.set("score", newScore);

    score.save(null, {
      success: function(success)
      {
        // Score was saved.
        // Find devices associated with 'to User'
        var queryTo = new Parse.Query(Parse.User);
        queryTo.equalTo("objectId", to.id);
        var pushQueryTo = new Parse.Query(Parse.Installation);
        pushQueryTo.matchesQuery("user", queryTo);

        pushQueryTo.first({
          success: function(installation)
          {
            // Device found
            // Fetch 'from User' infos
            from.fetch({
              success: function(User) {

                // 'from User' fetched
                // Send Push to 'to User'
                var first_name = User.get("first_name");

                var preferredLanguages = installation.get("preferredLanguages");
                var alert = ""

                switch (preferredLanguages) {
                  case "fr":
                    alert = first_name + " vous demande de : " + action
                    break;
                  default:
                    alert = first_name + " is asking you to : " + action
                }

                Parse.Push.send({
                    where: pushQueryTo,
                    data: {
                      "alert": alert,
                      "badge": "Increment",
                      "content-available": "1",
                      "type": "actionAsked",
                      "sound": "default"
                    }
                  });

                  // Everything is done!
                  response.success();

              },
              error: function(error) {
                // An error occurred.
                response.error(error);
              }
            });
          },
          error: function(error)
          {
            // An error occurred.
            response.error(error);
          }
        });
      },
      error: function(error)
      {
        // An error occurred.
        response.error(error);
      }
    });

  },
  error: function(error)
  {
    // An error occurred.
    response.error(error);
  }
});

Chain Promises Cloud Code (not working) :

Parse.Cloud.useMasterKey();
var action = request.object.get("action");
var from = request.object.get("from");
var to = request.object.get("to");
var reward = request.object.get("reward");

// Query 'from User' to decrement his score
var queryScore = new Parse.Query(Parse.User);
queryScore.get(from.id).then(function(score) {

    // Decrement score of 'fromUser'.
    var newScore = score.get("score");
    newScore -= reward;
    score.set("score", newScore);

    return score.save();

}).then(function(result) {

    // Score was saved.
    // Find devices associated with 'to User'
    var queryTo = new Parse.Query(Parse.User);
    queryTo.equalTo("objectId", to.id);
    var pushQueryTo = new Parse.Query(Parse.Installation);
    pushQueryTo.matchesQuery("user", queryTo);

    return pushQueryTo.first();

}).then(function(device) {

    // Device found
    // Fetch 'from User' infos

    return from.fetch();

}).then(function(from){

    // 'from User' fetched
    // Send Push to 'to User'
    var first_name = from.get("first_name");
    var preferredLanguages = device.get("preferredLanguages");
    var alert = ""

    switch (preferredLanguages) {
        case "fr":
            alert = first_name + " vous demande de : " + action
            break;
        default:
            alert = first_name + " is asking you to : " + action
    }

    Parse.Push.send({
        where: pushQueryTo,
        data: {
            "alert": alert,
            "badge": "Increment",
            "content-available": "1",
            "type": "actionAsked",
            "sound": "default"
        }
    });

    // Everything is done!
    response.success();

},function(error) {

    // An error occurred.
    response.error(error);

});

The error I get :

Apparently my error is concerning the "device" variable.

MatthieuTnsc
  • 97
  • 10

1 Answers1

4

In converting to promises you have also un-nested, causing device and pushQueryTo no longer to be in scope in later handlers. With nicely indented code (as in the question), scope issues like this are readily observable in the source.

Fortunately, wherever a then() handler returns a promise, you are afforded some freedom to chain directly to the expression that returns that promise, instead of adding to the outermost promise chain.

A fix here is simply to re-introduce some of the nesting by chaining directly to pushQueryTo.first() and from.fetch().

Parse.Cloud.useMasterKey();
var action = request.object.get("action");
var from = request.object.get("from");
var to = request.object.get("to");
var reward = request.object.get("reward");

var queryScore = new Parse.Query(Parse.User);
queryScore.get(from.id)
.then(function(score) {
    score.set('score', score.get('score') - reward);
    return score.save();
})
.then(function(result) {
    var queryTo = new Parse.Query(Parse.User);
    queryTo.equalTo('objectId', to.id);
    var pushQueryTo = new Parse.Query(Parse.Installation);
    pushQueryTo.matchesQuery('user', queryTo);
    return pushQueryTo.first()
    .then(function(device) {
        return from.fetch()
        .then(function(from) {
            var alert;
            switch (device.get('preferredLanguages')) {
                case 'fr': alert = from.get('first_name') + ' vous demande de : ' + action; break;
                default: alert = from.get('first_name') + ' is asking you to : ' + action;
            }
            Parse.Push.send({
                where: pushQueryTo,
                data: { 'alert': alert, 'badge': 'Increment', 'content-available': '1', 'type': 'actionAsked', 'sound': 'default' }
            });
            response.success();
        });
    });
}).catch(function(error) {
    response.error(error);
});

Please note that I said this is "a" solution, not "the" solution. In other words, other approaches exist. The whole topic of accessing previous promise results in a .then() chain is discussed very comprehensively here .

Community
  • 1
  • 1
Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
  • Thanks for you complete answer. I just had to change the error function to make it work `},function(error) { response.error(error); }); ` – MatthieuTnsc Aug 03 '15 at 12:43
  • @MatthieuTnsc , are you sure that's necessary? A single `catch()` as I wrote it, should catch any error, even from the inner promise chain. An inner catch would typically be necessary only if it treated the error differently from the outer - eg an intermediate error recovery. – Roamer-1888 Aug 03 '15 at 12:50
  • I get the following error with the catch error syntax --> `Error: TypeError: Object [object Object] has no method 'catch' at main.js:50:15` – MatthieuTnsc Aug 03 '15 at 13:02
  • Ah, OK I misunderstood your comment. It appears that Parse promises do not have a `catch()` method. – Roamer-1888 Aug 03 '15 at 13:15
  • thanks for this lovely answer. @Roamer-1888, you may possibly have advice on [this](http://stackoverflow.com/questions/32768731) puzzle I'm facing :O – Fattie Sep 25 '15 at 12:20