NtsDK NtsDK - 1 year ago 80
Javascript Question

Converting callback chain with data dependencies to promises

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 = {};
res.alpha = alpha;
return getBeta();
res.beta = beta;
return getOmega();
res.omega = omega;
return getEpsilon(res.beta);
res.epsilon = epsilon;
return getDelta(res.beta);
res.delta = delta;
console.log([res.alpha, res.beta, res.omega, res.epsilon, res.delta].join(' '));

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){
res = res || {};
resolve(R.merge(res, R.zipObj(promises[0], results)));

'alpha' : getAlpha(),
'beta' : getBeta()
}).then(function(res){ // res: { alpha: 'alpha', beta: 'beta' }
return promiseObj({
'epsilon' : getEpsilon(res.beta),
}, 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.

Answer Source

Not terribly elegant:

  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];