mac_55
mac_55

Reputation: 4250

Are my promises working as intended?

I'm writing a function in Parse's Cloud Code which populates the server with 5 playing cards. The idea being that the server should wait until all 5 cards are in the database before moving on to the next part of the function (which currently just outputs a message to the console.

What I'm seeing is that all 5 cards are being added to the database. They also do not necessarily appear in the database in sequence, which makes me believe they are correctly being added asynchronously. And for the mostpart my logging shows intended behaviour.

I2015-01-03T17:08:01.244Z] Attempting to create cards x5
I2015-01-03T17:08:01.245Z] Attempting to save card0
I2015-01-03T17:08:01.247Z] Attempting to save card1
I2015-01-03T17:08:01.248Z] Attempting to save card2
I2015-01-03T17:08:01.249Z] Attempting to save card3
I2015-01-03T17:08:01.250Z] Attempting to save card4
I2015-01-03T17:08:01.352Z] Card saved:4
I2015-01-03T17:08:01.353Z] Card saved:4
I2015-01-03T17:08:01.354Z] Card saved:4
I2015-01-03T17:08:01.355Z] Card saved:4
I2015-01-03T17:08:01.356Z] Card saved:4
I2015-01-03T17:08:01.357Z] ALL 5 Promises theoretically fulfilled

However, notice that when the cards are actually saved, the log for each one of them is using the same number - in this case "Card saved:4".

Question is... are my promises working as intended? and how do I fix my bug to show the actual card number that was saved?

Here's my code:

Parse.Cloud.define("populateServer", function(request, response)
{
    console.log("Attempting to create cards x5");
    var promises = createCards(5);

    Parse.Promise.when(promises).then(function(result)
    {
        console.log("ALL 5 Promises theoretically fulfilled");
    });
});

function createCards(qty)
{
    var promises = [];

    for (i=0;i<qty;i++)
    {
        var Card = Parse.Object.extend("Card");
        var card = new Card();

        card.set("name", "test");
        card.set("number", i);

        console.log("Attempting to save card" +i);

        var promise = card.save();

        promises.push(promise);

        promise.then(function() {
            console.log("Card saved:" +i);
        }, function(error) {
            console.log("Uh oh, something went wrong.");
        });
    }
    return promises;
}

Upvotes: 0

Views: 65

Answers (2)

abl
abl

Reputation: 5958

As Nick said, your counter (i) is incremented up to 4 before any of the then handlers execute, so naturally all handlers use the final value of i (which is 4).

The quickest workaround around this issue is probably to enclose the assignation of the then handler in a closure, in which you can use a variable to remember the value of i at the time the closure was called.

For example, replace this:

promise.then(function() {
    console.log("Card saved:" +i);
}, function(error) {
    console.log("Uh oh, something went wrong.");
});

With this:

(function(){
    var myIndex = i;
    promise.then(function() {
        console.log("Card saved:" + myIndex);
    }, function(error) {
        console.log("Uh oh, something went wrong.");
    });
})();

I've made a couple fiddles to see the difference in action: Without closure - With closure

Upvotes: 2

Nick Tomlin
Nick Tomlin

Reputation: 29211

You actually are getting the behavior you desire, but i is always 4 at the end of your promises because it is incremented before any of the then handlers execute (your for loop is synchronous but your handlers are asynchronous).

Normally promises passed to all are not resolved synchronously, but since you are creating a chain by thening of each promise your calls will only fire after the preceeding one has completed.

On a side note: I am not super familiar with Parse's promise implementation, but you can probably save yourself creating an extra array and calling Parse.Promise.when by just returning the promise chain from your function:

function createCards(qty) {
      // create a resolved promise that starts the "chain"
      // for Parse prommises use Parse.Promise.as();
      var promiseChain = Parse.Promise.as();

      // start the for loop
      for (i=0;i<qty;i++) {
        // add another promise onto our chain
        // you can `then` or `catch` off of this
        // now if you want
        promiseChain = promiseChain.then(function () {
          // ... any additional logic
          card.save();
        });
      }

      return promiseChain;
}

// usage
createCards(5)
  .then(function () {
    console.log('finished');
  });

Upvotes: 2

Related Questions