2

I have written a series of Parse Promises and am now getting error 141 when I make a request to this cloud code function. I have tried placing success: / error: all over the function where I think they belong based on the Parse DOCS.

Request

 {
   "projectDescription": "Testing saveProject",
   "projectTitle": "This is only a test, in the event of a real post this will have an actual description",
   "isEmailEnabled": true,
   "shareEmails": [
     "max@gmail.com",
     "nat@gmail.com",
     "noob@gmail.com"
   ],
   "userId": "sLmOf4fZFL"
 }

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

    var emails = request.params.shareEmails;
    var user = request.params.userId;
    var projectDescription = request.params.projectDescription;
    var projectTitle = request.params.projectTitle;
    var emailStatus = request.params.isEmailEnabled;

    var ProjectClass = Parse.Object.extend("Project");

    var EmailsClass = Parse.Object.extend("Email");

    var EmailsClassAssignment = Parse.Object.extend("EmailAssignment");

    var project = new ProjectClass();

    var projectO;


        project.set("title", projectTitle);
        project.set("createdBy", {
                        "__type": "Pointer",
                        "className": "_User",
                        "objectId": user
        });
        project.set("description", projectDescription);
        project.set("status", true);
        project.set("emailShareEnabled", emailStatus);
        project.save().then(function(results) {

            projectO = results;

            console.log(projectO);

        return Parse.Promise.when(emails.map(function(emailAddress) {

                var email = new EmailsClass();
                email.set("address", emailAddress);
                return email.save();

            }));
        }).then(function() {
        return Parse.Promise.when(emails.map(function(emailQuery) {

            var queryEmail = new Parse.Query("Email");
            queryEmail.equalTo("address", emailQuery);
            return queryEmail.find().then(function(results) {

                var emailJSON = results[0].toJSON();
                var emailObjectId = emailJSON.objectId;

                var projectJSON = projectO.toJSON();

                var projectId = projectJSON.objectId;

                var assignment = new EmailsClassAssignment();

                assignment.set("createdBy", {
                                    "__type": "Pointer",
                                    "className": "_User",
                                    "objectId": user
                });
                assignment.set("email", {
                                    "__type": "Pointer",
                                    "className": "Email",
                                    "objectId": emailObjectId
                });
                assignment.set("project", {
                                    "__type": "Pointer",
                                    "className": "Project",
                                    "objectId": projectId
                });
                assignment.save(null, {
                    success: function() {
                        console.log("Successfully saved project");
                    },
                    error: function(error) {
                        console.log("There was an error saving" + error.message);
                    }
                });
            });
        }));
    }).then( function() {
        response.success();
    });
});
mcclaskiem
  • 394
  • 1
  • 15
  • Hm, [those highlighting comments look familiar](http://stackoverflow.com/a/28943960/1048572) :-) Please remove them if they don't emphasize any aspect of your question. – Bergi Mar 25 '15 at 20:33
  • You're missing a return before `project.save().then(`. I strongly advise you to consider the refactoring per @danh's answer. – kingdango Mar 26 '15 at 13:22
  • Yes definitely, I am implementing and testing it now. Only 3 months into the developer game so am still learning! This was some great advice from @danh! – mcclaskiem Mar 26 '15 at 13:26
  • Hey, promises are tricky. This EXACT scenario has happened to me at least a dozen times. Keep at it! :) – kingdango Mar 26 '15 at 13:33

2 Answers2

4

The basic ideas look okay, but the code is kind of a jumble of callback parameters and promises. I took the liberty of refactoring into simpler, promise-returning logical chunks so we could see what's going on.

You highlighted the .map functions in the post. Not sure what the issue was there, so the code I suggest uses underscorejs, which can be easily included in the cloud as follows:

var _ = require('underscore');

First, return a promise to save a "project" given most of the params to your cloud function:

function createProject(params) {
    var ProjectClass = Parse.Object.extend("Project");
    var project = new ProjectClass();

    var emails = request.params.shareEmails;
    var user = request.params.userId;
    var projectDescription = request.params.projectDescription;
    var projectTitle = request.params.projectTitle;
    var emailStatus = request.params.isEmailEnabled;

    project.set("title", projectTitle);
    project.set("createdBy", {
                    "__type": "Pointer",
                    "className": "_User",
                    "objectId": user
    });
    project.set("description", projectDescription);
    project.set("status", true);
    project.set("emailShareEnabled", emailStatus);
    return project.save();
}

Next, create "Email"'s (which are objects) given an array of email address strings. (You would do well to more carefully distinguish the objects and the strings in your naming, but I tried to hew to original nomenclature in the code)

function createEmails(emails) {
    var EmailsClass = Parse.Object.extend("Email");
    var toSave = _.map(emails, function(emailAddress) {
        var email = new EmailsClass();
        email.set("address", emailAddress);
        return email;
    });
    // like the when() function, but (possibly) fewer requests
    return Parse.Object.saveAll(toSave);
}

This is where the original code took a turn for the worse. In it, the code just finished creating the Email objects, then for some reason, it attempts to query those objects. But we have them in hand already, on the fulfullment of the promises to save.

The method below, takes already built email objects (named pedantically, to emphasize that they are objects) and other ingredients to an "EmailClassAssignment". Notice how we can assign pointers directly with objects when we have a PFObject in hand:

function createEmailClassAssignments(emailObjects, project, userId) {
    var EmailsClassAssignment = Parse.Object.extend("EmailAssignment");
    var toSave = _.map(emailObjects, function(emailObject) {
        var assignment = new EmailsClassAssignment();
        // the real objects can be used as parameters to set for pointer columns
        assignment.set("email", emailObject);
        assignment.set("project", project);
        // we only have the userId, not a user object, so we can either query
        // for the user or take the shortcut that you've been taking
        project.set("createdBy", {
                        "__type": "Pointer",
                        "className": "_User",
                        "objectId": user
        });
        return assignment;
    });
    return Parse.Object.saveAll(toSave);
}

With all that done, the cloud function becomes more legible:

Parse.Cloud.define("saveProject", function(request, response) {
    var project;
    createProject(params).then(function(result) {
        project = result;
        return createEmails(request.params.shareEmails);
    }).then(function(emailObjects) {
        return createEmailClassAssignments(emailObjects, project, request.params.userId);
    }).then(function() {
        console.log("Successfully saved project");
        // I took the liberty of returning the new project to the caller
        response.success(project);
    }, function(error) {
        console.log("There was an error saving" + error.message);
        resoonse.error(error);
    });
});

CAUTION: obviously, there's no way for me to test any of the foregoing. I strongly urge you to test the functions yourself, preferably individually before expecting the combination to work. Hopefully, the refactor demonstrates a cleaner way to use promises and a reasonable decomposition of parts to test and use individually.

danh
  • 62,181
  • 10
  • 95
  • 136
  • Thanks for this answer! Breaking up the code like this definitely makes it a lot easier to understand and maintain. I have never used underscore before so am just curious how that plays into writing the code/added value of using it. – mcclaskiem Mar 26 '15 at 13:23
  • Worked perfectly out of the box! Thanks again, very much appreciated! Wish I could upvote more! – mcclaskiem Mar 26 '15 at 13:32
  • So I have a beforeSave function that prevents the creation of duplicate emails based on the address key/value. If an email being passed with a project already exists in the "Email" class, is there a simple way to returning the object and passing it to the EmailClassAssignment? – mcclaskiem Mar 26 '15 at 13:43
  • `query.equalTo("address", eachEmail); // Checks to see if an object for that email already exists query.first({ success: function(object) { if (object) { // pass object here } else { // create the object and pass it down } }, error: function(error) { response.error("Could not determine if this email exists"); } });` – mcclaskiem Mar 26 '15 at 13:45
  • @mcclaskiem - are you aiming to catch the duplicates before the beforeSave? Maybe we should break this out into its own question? – danh Mar 26 '15 at 15:59
  • working on a new question now! Thanks for getting back to me! – mcclaskiem Mar 26 '15 at 16:21
  • here is new question http://stackoverflow.com/questions/29283859/pass-exisiting-object-from-parse-class-otherwise-create-a-new-object @danh – mcclaskiem Mar 26 '15 at 16:29
2

From the looks of your code, you simply need to add a return in front of assignment.save() as you aren't waiting for that to finish otherwise.

Lastly you should add an error catcher at the very end:

.then(null, function(error) {
  console.log(error);
  response.error(error);
});
Timothy Walters
  • 16,866
  • 2
  • 41
  • 49
  • 1
    I agree with this answer but OP should consider the refactoring @danh did. No offense but OPs original code is a bit messy (that's why you missed the return). Danh's answer demonstrates a basic encapsulation approach that you should try to use when working with promises. – kingdango Mar 26 '15 at 13:34
  • Ya the refactoring is great much cleaner and easier to see whats going on as well an ensure the success/error callbacks are working – mcclaskiem Mar 26 '15 at 13:38