Efficient sequencing of async & sync methods using promise/deferred (jQuery)

Efficient sequencing of async & sync methods using promise/deferred (jQuery)
0.0 0

#1

I’m working on a project using an API to collect data and then paint html to represent the data; there are 3 asynchronous methods used to gather the data and of course related synchronous methods to paint the html for each.

I’m hoping to get responses here to help me make sure I’m doing this work efficiently – the code I’ll be displaying works and thus my project is not being held up by any issues relating to this code, however I’m not certain of its efficacy.

For the 3 async methods – let’s call them async1, async2, async3 (or respectfully truncated as a1, a2, a3) – assume the following:

each async returns an array of objects; the results of a1 are then iterated for a2 and a3, and all results of each async are pushed to their own ‘master’ array; the resulting arrays are then iterated for sync methods to paint html content to the page, such that each a1 object has its own container, within which are containers from the related objects of a2, within which are containers from the related objects of a3.

Here’s a pseudo code representation of what I have (assume all this is wrapped within a single function):


//establish master arrays for each async method result
var a1array = [], a2array = [], a3array = [];

//establish Deferreds for each async master array, html
var a1arrDfd = $.Deferred(), a1htmlDfd = $.Deferred();
var a2arrDfd = $.Deferred(), a2htmlDfd = $.Deferred();
var a3arrDfd = $.Deferred(), a3htmlDfd = $.Deferred();

//get a1 data
async1(params).done(fn(a1data) {

  //iterate over a1 return and push to a1 master array
  var i, ilen = a1data.length;
  for (i = 0; i < ilen; i++) {
    a1array.push(a1data[i]);

    //if at last iteration of a1data array,
    //a1's master array is complete
    if (i + 1 === ilen) { a1arrDfd.resolve(); }
  }
}).done(fn(a1data) {
  var j, jlen = a1data.length, jdone = 0;

  //loop thru a1data array to get a2 data and
  //push a2data to a2 master array
  for (j = 0; j < jlen; j++) {
    async2(param + a1data[j].key + param).done(fn(a2data) {
      var k, klen = a2data.length, kdone = 0;
      jdone += 1;

      //loop thru a2data array to push to a2 master array
      for (k = 0; k < klen; k++) {
        a2array.push(a2data[k]);
        kdone += 1;

        //if at last a1data iteration and a2data iterant,
        //a2's master array is complete
        if (jdone === jlen && kdone === klen) { a2arrDfd.resolve(); }
      }
    });
  }
}).done(fn(a1data) {
  var l, llen = a1data.length, ldone = 0;

  //loop thru a1data array to get a3 data and
  //push a3data to a3 master array
  for (l = 0; l < llen; l++) {
    async3(param + a1data[l].key + param).done(fn(a3data) {
      var m, mlen = a3data.length, mdone = 0;
      ldone += 1;

      //loop thru a3data array to push to a3 master array
      for (m = 0; m < mlen; m++) {
        a3array.push(a3data[m]);
        mdone +=1;

        //if at last a1data iteration and a3data iterant
        //a3's master array is complete
        if (ldone === llen && mdone === mlen) { a3arrDfd.resolve(); }
      }
    });
  }
});

//listen for completion of async1 array, then
//paint html containers for each object in async1 array
$.when(a1arrDfd).done(fn() {
  var n, nlen = a1array.length;
  for (n = 0; n < nlen; n++) {
    a1paintHtml;

    //if at last a1array iteration,
    //a1's html painting is complete
    if (n + 1 === nlen) { a1htmlDfd.resolve(); }
  }
});

//listen for completion of async1 html containers and
//async2 array, then paint containers for
//each object in async2 array
$.when(a1htmlDfd, a2arrDfd).done(fn() {
  var o, olen = a2array.length;
  for (o = 0; o < olen; o++) {
    a2paintHtml;

    //if at last a2array iteration,
    //a2's html painting is complete
    if (o + 1 === olen) { a2htmlDfd.resolve(); }
  }
});

//listen for completion of async2 html containers and
//async3 array, then paint containers for
//each object in async3 array
$.when(a2htmlDfd, a3arrDfd).done(fn() {
  var p, plen = a3array.length;
  for (p = 0; p < plen; p++) {
    a3paintHtml;

    //if at last a3array iteration,
    //a3's html painting is complete
    if (p + 1 === plen) { a3htmlDfd.resolve(); }
  }
});

Note: if you are inclined to respond with setTimeout, please provide an explanation as to why – IMO, using the setTimeout method seems arbitrary particularly when considering how to determine what to use as the delay param, so you’d have to educate me as to how my assumption in this regard is incorrect.

Thanks in advance for any suggestions


#2

This is out of my depth, but have you tried code review on stack-exchange? This seems like a good problem to ask over there


#3

thanks, I’ve thought of doing that but figured to test the waters here first


#4

for anyone interested in following this further, I’ve decided to post this on stack overflow, the post is here: http://stackoverflow.com/questions/43829076/efficient-sequencing-of-async-sync-methods-using-promise-deferred


#5

After nearly 2 months I’ve revisited this project armed with more knowledge regarding Promises in JS, and I now have the following which is rid of the deferred antipattern:

const a1array = [], a2array = [], a3array = [];

const a1get = async1(params).then(function(data) {
  const len = data.length;
  for (let i = 0; i < len; i++) {
    a1array.push(data[i]);
  }
  return a1array;
});

const a2get = a1get.then(function(a1data) {
  const promiseArray = a1data.map(function(a1) {
    return async2(param + a1.key + param)
      .then(function(data) {
        const len = data.length;
        for (let i = 0; i < len; i++) {
          a2array.push(data[i]);
        }
      });
  });
  return Promise.all(promiseArray);
});

const a3get = a1get.then(function(a1data) {
  const promiseArray = a1data.map(function(a1) {
    return async3(param + a1.key + param)
      .then(function(data) {
        const len = data.length;
        for (let i = 0; i < len; i++) {
          a3array.push(data[i]);
        }
      });
  });
  return Promise.all(promiseArray);
});

Promise.all([a1get, a2get, a3get]).then(function() {
  a1paintHtml();
  a2paintHtml();
  a3paintHtml();
});

IMO this is clearly a more effective use of Promises, but of course if anyone sees something amiss please do let me know…