Search code examples
javascriptparse-platformpromiseparse-cloud-code

Cleaning up Spaghetti Code w/ Promises


In one of my beforeSave functions, I need to check a number of conditions before responding with success or error.

The problem is, my code seems really messy and sometimes success/error isn't called:

Parse.Cloud.beforeSave("Entry", function(request, response) {
    var entry = request.object;
    var contest = request.object.get("contest");

    entry.get("user").fetch().then(function(fetchedUser) {
      contest.fetch().then(function(fetchedContest) {
            if ( fetchedUser.get("fundsAvailable") < fetchedContest.get("entryFee") ) {
              response.error('Insufficient Funds.');
            } else {
                fetchedContest.get("timeSlot").fetch().then(function(fetchedTimeSlot) {
                    var now = new Date();
                    if (fetchedTimeSlot.get("startDate") < now) {
                        response.error('This contest has already started.');
                    } else {
                        contest.increment("entriesCount");
                        contest.increment("entriesLimit", 0); //have to do this, otherwise entriesLimit is undefined in save callback (?)

                        contest.save().then(function(fetchedContest) {
                            if (contest.get("entriesCount") > contest.get("entriesLimit")) {
                                response.error('The contest is full.');
                            } else {
                                response.success();
                            }
                        });
                    }
                });
            }
        });
    });
});

I've been trying to learn promises to tidy this up, and here was my (failed) attempt:

Parse.Cloud.beforeSave("Entry", function(request, response) {
    var entry = request.object;
    var contest = request.object.get("contest");

    entry.get("user").fetch().then(function(fetchedUser) {
        contest.fetch().then(function(fetchedContest) {
            if ( fetchedUser.get("fundsAvailable") < fetchedContest.get("entryFee") ) {
              response.error('Insufficient Funds.');
            }
            return fetchedContest;
        });
    }).then(function(result) {
        result.get("timeSlot").fetch().then(function(fetchedTimeSlot) {
            var now = new Date();

            if (fetchedTimeSlot.get("startDate") < now) {
                response.error('This contest has already started.');
            }
        });
    }).then(function() {
        contest.increment("entriesCount");
        contest.increment("entriesLimit", 0);
        contest.save().then(function(fetchedContest) {
            if (contest.get("entriesCount") > contest.get("entriesLimit")) {
                response.error('The contest is full.');
            }
        });
    }).then(function() {
        response.success();
    }, function(error) {
        response.error(error);
    });
});

Any help or examples on how to use promises for this would be much appreciated. Clearly I do not fully understand how they work syntactically yet.


Solution

  • I cleaned it up a bit by getting the fetched variables assembled first using a chain of promises. This follows a couple style rules that make it more readable (for me anyway)...

    Parse.Cloud.beforeSave("Entry", function(request, response) {
        var entry = request.object;
        var contest = request.object.get("contest");
    
        var fetchedUser, fetchedContest;
        var errorMessage;
    
        entry.get("user").fetch().then(function(result) {
            fetchedUser = result;
            return contest.fetch();
        }).then(function(result) {
            fetchedContest = result;
            return fetchedContest.get("timeSlot").fetch();
        }).then(function(fetchedTimeSlot) {
            // now we have all the variables we need to determine validity
            var now = new Date();
            var hasSufficientFunds = fetchedUser.get("fundsAvailable") >= fetchedContest.get("entryFee");
            var contestNotStarted = fetchedTimeSlot.get("startDate") >= now;
            if (hasSufficientFunds && contestNotStarted) {
                contest.increment("entriesCount");
                contest.increment("entriesLimit", 0); //have to do this, otherwise entriesLimit is undefined in save callback (?)
                return contest.save();
            } else {
                errorMessage = (hasSufficientFunds)? 'This contest has already started.' : 'Insufficient Funds.';
                return null;
            }
        }).then(function(result) {
            if (!result) {
                response.error(errorMessage);
            } else {
                if (contest.get("entriesCount") > contest.get("entriesLimit")) {
                    response.error('The contest is full.');
                } else {
                    response.success();
                }
            }
        }, function(error) {
            response.error(error);
        });
    });
    

    Note how we never leave a response.anything() dangling, we always make clear what's flowing to the next promise via a return.