1

I have an async javascript app for browser and I want to use promises. Every async call has same pattern which can be transformed in promise. My problem is that I have data dependencies in callback chain. Some simplified code:

getAlpha(function(err, alpha){
        if(err) {handleError(err); return;}
        getBeta(function(err, beta){
                if(err) {handleError(err); return;}
                getOmega(function(err, omega){
                        if(err) {handleError(err); return;}
                        getEpsilon(beta, function(err, epsilon){ // depends on beta
                                if(err) {handleError(err); return;}
                                getDelta(beta, function(err, delta){ // depends on beta
                                        if(err) {handleError(err); return;}
                                        console.log(alpha + beta + omega + epsilon + delta);
                                });
                        });
                });
        });
});

I see two problems with promises here:

  1. There are data dependencies between callback calls. getEpsilon and getDelta depends on beta value.

  2. I need all collected data in the last callback from the very first call and other.

I look here http://stuk.github.io/promise-me/ for some examples. "Captured variables" example solve both problems but it makes the same callback ladder which we see without promises.

Other way is making data object to store all promise returns. It looks like this:

var res = {};
getAlpha().then(function(alpha){
    res.alpha = alpha;
    return getBeta();
}).then(function(beta){
    res.beta = beta;
    return getOmega();
}).then(function(omega){
    res.omega = omega;
    return getEpsilon(res.beta);
}).then(function(epsilon){
    res.epsilon = epsilon;
    return getDelta(res.beta);
}).then(function(delta){
    res.delta = delta;
    console.log([res.alpha, res.beta, res.omega, res.epsilon, res.delta].join(' '));
}).catch(function(err){
    handleError(err);
});

I wonder if it is possible to solve this problem without nested calls and data container.

UPD 1. I'm very sorry but my first promise solution doesn't work at all so I make correct version.

UPD 2. In original code each get-LETTER call is a GET http request so these calls can work in parallel.

UPD 3. Thanks to robertklep and Jeff Bowman. I tried both answers. I like robertklep version because it is very short. After Jeff Bowman version I understand many potential issues in my asynchronous execution.

UPD 4. I mixed my initial promise solution with robertklep version to add some sugar.

var R = require('ramda');

// getAlpha returns alpha, etc.

var promiseObj = function(obj, res){
    var promises = R.transpose(R.toPairs(obj));
    return new Promise(function(resolve, reject){
        Promise.all(promises[1]).then(function(results){
            res = res || {};
            resolve(R.merge(res, R.zipObj(promises[0], results)));
        });
    });
};

promiseObj({
    'alpha' : getAlpha(),
    'beta' : getBeta()
}).then(function(res){ // res: { alpha: 'alpha', beta: 'beta' }
    return promiseObj({
        'epsilon' : getEpsilon(res.beta), 
    }, res);
}).then(function(res){
    console.log(res); // res: { alpha: 'alpha', beta: 'beta', epsilon: 'beta_epsilon' }
});

P.S. How do I access previous promise results in a .then() chain? gives answer to my question too but in more generic way.

Community
  • 1
  • 1
NtsDK
  • 951
  • 1
  • 9
  • 19
  • You can pass the object down the chain, then destruct the object in each promise function. If you want to keep them independent though, you can pass the array then destruct it, execute the function, insert new value in array and pass it to next promise – Yerken Jun 15 '16 at 15:07
  • 1
    If you just need to pass the value of the previous promise to the next, the object you used is not needed you can return a new promise in the previous callback and chain the next promise. getAlpha.then(getBeta).then(getDelta); – Jose Hermosilla Rodrigo Jun 15 '16 at 16:11
  • 5
    Oops, I meant to close as a duplicate of [How do I access previous promise results in a .then() chain?](http://stackoverflow.com/q/28250680/1048572) – Bergi Jun 15 '16 at 18:19

2 Answers2

2

Not terribly elegant:

Promise.all([
  getAlpha(),
  getOmega(),
  getBeta().then((beta) => {
    return Promise.all([ beta, getEpsilon(beta), getDelta(beta) ]);
  })
]).then((results) => {
  let alpha   = results[0];
  let omega   = results[1];
  let beta    = results[2][0];
  let epsilon = results[2][1];
  let delta   = results[2][2];
  ...
});
robertklep
  • 198,204
  • 35
  • 394
  • 381
  • 1
    Notably, this makes all three (alpha, omega, and beta) occur in parallel rather than in sequence, which may not be a bad thing, but isn't a property of either version in the question. – Jeff Bowman Jun 15 '16 at 17:02
  • @JeffBowman thanks for mentioning that! I assumed from the question that concurrency wasn't going to be a problem, but it's better to be explicit about it :-) – robertklep Jun 15 '16 at 17:07
1

(Assume here that each of getAlpha, getBeta, getOmega, getEpsilon, and getDelta returns a promise.)

In sequence:

function getInSequence() {
  // Get alpha immediately.
  let alphaPromise = getAlpha();
  // After alpha returns, get beta.
  let betaPromise = alphaPromise.then(() => getBeta());
  // After beta returns, get omega.
  let omegaPromise = betaPromise.then(() => getOmega());
  // After omega returns, use then to get a value from beta (which has resolved
  // by now), which we feed into getEpsilon.
  let epsilonPromise = omegaPromise.then(() => betaPromise)
                                   .then(beta => getEpsilon(beta));
  // After epsilon returns, use then to get that beta value to pass along.
  let deltaPromise = epsilonPromise.then(() => betaPromise)
                                   .then(beta => getDelta(beta));
  // Return a promise that resolves into an array of everything.
  return Promise.all([alphaPromise, betaPromise, omegaPromise, 
                      epsilonPromise, deltaPromise])
                .catch(err => handleErr(err));
}

In parallel:

function getInSequence() {
  // Get alpha immediately.
  let alphaPromise = getAlpha();
  // Get beta immediately.
  let betaPromise = getBeta();
  // Get omega immediately.
  let omegaPromise = getOmega();
  // Get epsilon as soon as beta returns.
  let epsilonPromise = betaPromise.then(beta => getEpsilon(beta));
  // Get delta as soon as beta returns.
  let deltaPromise = betaPromise.then(beta => getDelta(beta));
  // Return a promise that resolves into an array of everything.
  return Promise.all([alphaPromise, betaPromise, omegaPromise,
                      epsilonPromise, deltaPromise])
                .catch(err => handleErr(err));
}
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251