3

I need to chain some asynch actions. I'm trying to write Parse.com cloud code which uses express. (I think express is where the promises support originates). I see why promises are valuable, but am still unsure about a couple things. The first is how to collect the results of sequenced actions. The code below illustrates:

function doAsnychThingsInSequence(params) {
  return doThing0(params).then(function(resultOfThing0) {
    // thing1 depends on resultOfThing0
    doThing1(resultOfThing0);
  }).then(function(resultOfThing1) {
    // here is where I am confused.
    // thing2 depends on the results of thing0 and thing1
    doThing2(resultOfThing0 /* out of scope?? */, resultOfThing1);
  }, function(error) {
    // handle error
  });
}

After thing1 is done, I need the results of both actions. I think I can allocate a variable at the top of the function and assign it to the first result in the first callback, is that the right way? I guess, at the heart of my confusion is question two...

  return doThing0(params0).then(function(resultOfThing0) {
    doThing1(resultOfThing0);
    // what does return mean here?  does what I return here relate to the
    // parameters of the next function?
    return "foo";
  }).then(function(param) {
    // what is in param?  is it "foo"?
  }, function(error) {
});
goodson
  • 727
  • 2
  • 14
  • 24

3 Answers3

1

Promises can be imagined as a stream processing: one function gets input, does something with it and pass it to the next function in the chain.

So if you need to pass input (for the chain) parameters further you should include them into the output data so next one in the chain can use them:

function doThing1(params1) {
  ...
  return [params1, result1];
}

function doThing2(params1, params2) {
  ...
}

As you've mentioned you can use some variable outside doThing1 and doThing2 but it will make these functions statefull that may lead to side effects of various kinds. Not desired in general for async processing.

c-smile
  • 26,734
  • 7
  • 59
  • 86
  • Thanks for the help. I guess this answer is more correct, but sorry to say that this is kind of a bummer for Promises. – goodson Feb 09 '14 at 20:11
  • @user3245272 Yeah, promises as a concept is still far from ideal, thinking... – c-smile Feb 09 '14 at 22:46
  • It seems like promises should have included a "context" argument that just gets passed through, but lets the code hang intermediate results there. Would there be some way to construct the array in the then block and let the doThing1 function just return it's single result? – goodson Feb 10 '14 at 02:44
  • Sadly, the [Parse.com API docs](http://parse.com/docs/js/symbols/Parse.Promise.html) don't say that they behave this way (they also don't say they *don't*). The [A+ Promise spec](http://promises-aplus.github.io/promises-spec/) (which may or may not be what Parse.com is trying to adhere to) says that `doThing2`, in your example, would receive a *single* argument that was the array returned by `doThing1`, rather than discrete args. Do you know that it really receives discrete values on Parse.com's implementation? – T.J. Crowder Feb 10 '14 at 07:40
  • @user3245272: You could have each of them expect to receive, and return, an object on which they set their params/results as properties. (Creating the object if they don't receive it; e.g., if they're the first in the chain.) But I don't know that that buys you anything on top of using the context they close over. The point of `then`, though, is that it's specifically to allow stateful logic: It's called when the promise it's waiting on is finished. So relying on information from the promise(s) you're waiting on (using closure vars or a context argument) is reasonable. – T.J. Crowder Feb 10 '14 at 08:38
1

The function supplied to then() must return a value, whether that value is the value to be used, or a promise for an upcoming value. Either way (per the promises spec) then() will return a new promise. That's the first issue I see with your code.

Next is that you have to store thing1 in a higher scope so that you can access it later. So something like this might be in order:

// example
var thing1;
getThing1()
.then(function(value){
  thing1 = value; // <-- store in higher scope
  return getThing2(); // <-- return
})
.then(function(value){
  thing2 = value;
  // now you have both thing1 and thing2 in scope
})
greim
  • 9,149
  • 6
  • 34
  • 35
  • Thanks for the help. And thanks for the catch on the return value. I've summarized what I learned below. – goodson Feb 09 '14 at 20:11
1

I'd like to summarize what I've learned from the two helpful answers from @greim and @c-smile. +1 both for the kind help. But If I'm understanding correctly, then this is a disappointment for me about Promises.

@greim's answer will make my callbacks refer to local variables in the containing function. But this is disappointing because it makes the callback dependent those locals. It would be hard, for example, to pass in a callback.

@c-smile's answer will make my functions return an array (or some collection) of their parameters and their result. All callers of that function would then be expected to dig through the return value for the "natural" result as opposed to the params that it used to get the result.

Restating the answers in terms of my original post:

// @greim

function doAsnychThingsInSequence(params) {
  var theResultOfThing0;  // yuck, for needing to make up a non-colliding variable name
  return doThing0(params).then(function(resultOfThing0) {
    theResultOfThing0 = resultOfThing0; 
    return doThing1(resultOfThing0);
  }).then(function(resultOfThing1) {
    return doThing2(theResultOfThing0, resultOfThing1);
  }, function(error) {
    // handle error
  });
}

// @c-smile

function doThing1(params) {  // params are resultOfThing0
  // do stuff
  // the "natural" answer is just resultOfThing1, but ...
  return [params, resultOfThing1];
}

// if this function was doThingN, then we'd have
  return [params0, params1 ... paramsN, resultOfThingN];  // yikes!

// at least this method looks great now, how I want it to look...
function doAsnychThingsInSequence(params) {
  return doThing0(params).then(function(resultOfThing0) {
    return doThing1(resultOfThing0);
  }).then(function(resultOfThing0, resultOfThing1) {
    return doThing2(resultOfThing0, resultOfThing1);
  }, function(error) {
    // handle error
  });
}
goodson
  • 727
  • 2
  • 14
  • 24