javascriptparse-platformparse-javascript-sdkparse-cloud-code

Parse cloud function - code ot executed in sequence


I have below function to update rows if meeting some condition and at the end of for loop, response include how many rows updated.Despite more than zero rows updated, response shows zero. Looking at log, it seems reponse.success() fires before completing for loop.

why so?

Parse.Cloud.define("reset", function(request, response) {


    var isSaveNeeded = false
  var Query = new Parse.Query("price");
  Query.equalTo('isActive', true);
  Query.find({useMasterKey:true})
      .then((results) => {
        console.log("Found " + results.length + " price rows")
        var currentDate = moment()
        var noOfRowsUpdated = 0
        for (let i = 0; i < results.length; ++i) {
            var valid_till_date = results[i].get('valid_till_date');

            if (valid_till_date == null) {
                // if user had not selected valid_till_date then set to expire after system max no of days
                var updatedAt = results[i].get('updatedAt');
                if (currentDate.diff(updatedAt,'days') > 10) {
                  console.log("Permanent change row to be set inactive. Updated at - " + currentDate.diff(updatedAt)+ updatedAt)
                    results[i].set('isActive',false)
                    isSaveNeeded = true
                }
            } else if (currentDate.diff(valid_till_date) > 0) {
                // check whether valid_till_date has passed
                console.log("Found row with elapsed valid date "  + results[i].id)
                results[i].set("isActive",false)
                isSaveNeeded = true
            }
            if (isSaveNeeded == true) {
              console.log("Record needs to be saved for " + results[i].id)
              results[i].save(null, {useMasterKey:true})
                .then(function (user) {
                    ++noOfRowsUpdated
                    console.log("reset : Object ID: " + results[i].id + " saved - " + noOfRowsUpdated)
                  })
                .catch(function (error) {
                    console.log("reset : Error saving Object ID: " + results[i].id + error);
                    response.error(error);
                })
            } else {

              console.log("Record not to be saved for " + results[i].id)
            }
            isSaveNeeded = false
        } // end of for loop
        //BELOW IS EXECUTED BEFORE FOR LOOP COMPLETES
        console.log("Updated " + noOfRowsUpdated +" price rows");
        response.success("Updated " + noOfRowsUpdated +" price rows")
      }) // end of .then((results) 
      .catch(function(error) {
        response.error("Failed to fetch from price" + error );
      });
});

Solution

  • Parse.com's save runs async, so that loop finishes before the saves happen. The solution is to reorganize the code a little bit, and wait for the saves to happen before executing the response functions.

    The trick is to collect the promises returned by each save in an array and wait for the fulfillment of those promises with Promise.when() (synonym for Promise.all())

    To make it clearer, factor out the "is save needed" logic, so this cloud function can be only about handling the database...

    Parse.Cloud.define("reset", function(request, response) {
        var Query = new Parse.Query("price");
        Query.equalTo('isActive', true);
        Query.find({useMasterKey:true}).then((results) => {
            console.log("Found " + results.length + " price rows");
            // assuming ES6 or something like underscore
            let pricesToSave = results.filter(price => priceNeedsSave(price));
    
            // here's the important part, collect the promise from each save
            // proceed only after the promises have completed
            let promises =  pricesToSave.map(price => price.save(null, {useMasterKey:true}));
            return Parse.Promise.when(promises).then(() => pricesToSave.length);
        }).then(count => {
            response.success("Updated " + count +" price rows");
        }).catch(error => {
            response.error("Failed to fetch from price" + error );
        });
    }
    

    Just for completeness, below is the factored-out needsSave logic. (OP should check this over, I just copied the body of the loop)...

    function priceNeedsSave(price) {
        var isSaveNeeded = false;
        var currentDate = moment()
    
        var valid_till_date = price.get('valid_till_date');
        if (valid_till_date == null) {
            // if user had not selected valid_till_date then set to expire after system max no of days
            var updatedAt = price.get('updatedAt');
            if (currentDate.diff(updatedAt,'days') > 10) {
              console.log("Permanent change row to be set inactive. Updated at - " + currentDate.diff(updatedAt)+ updatedAt)
                price.set('isActive',false)
                isSaveNeeded = true
            }
        } else if (currentDate.diff(valid_till_date) > 0) {
            // check whether valid_till_date has passed
            console.log("Found row with elapsed valid date "  + price.id)
            price.set("isActive",false)
            isSaveNeeded = true
        }
        return isSaveNeeded;
    }