1
\$\begingroup\$
/**
 * takes a list of componentIDs to load, relative to componentRoot
 * returns a promise to the map of (ComponentID -> componentCfg)
 */
function asyncLoadComponents (componentRoot, components) {

    var componentCfgs = {};

    function asyncLoadComponentCfg(component) {
        var url = _.sprintf("%s/%s", componentRoot, component);
        var promise = util.getJSON(url);
        promise.done(function(data) {
            componentCfgs[component] = data;
        });
        return promise;
    }

    var promises = _.map(components, asyncLoadComponentCfg);
    var flattenedPromise = $.when.apply(null, promises);
    var componentCfgPromise = flattenedPromise.pipe(function() {
        // componentCfgs is loaded now
        return $.Deferred().resolve(componentCfgs).promise();
    });

    return componentCfgPromise;
}


var locale = 'en-US';
var componentRoot = '/api/components';
var components = ['facets', 'header', 'DocumentList'];
function buildDocumentListPage(locale, componentCfgs) { /* code goes here */ }

$.when(asyncLoadComponents(componentRoot, components)).done(function(componentCfgs) {
    buildDocumentListPage(locale, componentCfgs)
});
\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

Here's a sample in jsFiddle, with some values and functions mocked-up to use jsFiddle API.

Here's the actual code, with notes in the comments:

//personally, I like comma separated variables preferrably the 
//"comma-before style". But it's personal preference.

//JS "hoists" up variable and function declarations higher in the scope.
//To avoid unexpected behaviour, and so that they are easier to find,
//we move them to the top of their scopes
var locale = 'en-US'
  , componentRoot = '/api/components'
  , components = ['facets', 'header', 'DocumentList']
  ;

function buildDocumentListPage(locale, componentCfgs) {}

//load our components
function asyncLoadComponents(componentRoot, components) {

      //our configuration collector
  var componentCfgs = {}

      //we create a promises array by mapping each value to a function
    , promises = _.map(components, function (component) {

        //we use the promise of a getJSON request (Assuming this is jQuery).
        //since you just concatenated the url values, we can just concatenate with +
        return util.getJSON(componentRoot+'/'+component).done(function (data) {

          //when getJSON resolves, we put the data in the collector
          componentCfgs[component] = data
        });
      });

  //as of jQuery 1.8, pipe is deprecated in favor of then. However, then
  //is designed to act like pipe, and instead of resolving with the value
  //we return the value instead
  return $.when.apply(null, promises).then(function () {
    return componentCfgs;
  })
}

//we model the function to return a promise that we'll listen to instead
//of having a $.when here. That way, we'll deal with less code and promises
asyncLoadComponents(componentRoot,components).done(function(componentCfgs) {

  //here, all configs have loaded and stored to componentCfgs
  buildDocumentListPage(locale, componentCfgs);
});

Packed code looks like this, tons shorter:

var locale = 'en-US',
  componentRoot = '/api/components',
  components = ['facets', 'header', 'DocumentList'];

function buildDocumentListPage(locale, componentCfgs) {}

function asyncLoadComponents(componentRoot, components) {
  var componentCfgs = {}, promises = _.map(components, function (component) {
      return util.getJSON(componentRoot + '/' + component).done(function (data) {
        componentCfgs[component] = data
      })
    });
  return $.when.apply(null, promises).then(function () {
    return componentCfgs
  })
}

asyncLoadComponents(componentRoot, components).done(function (componentCfgs) {
  buildDocumentListPage(locale, componentCfgs)
});
\$\endgroup\$
0
0
\$\begingroup\$

i have fully generalized thanks to Joseph's help. So this could parallelize and sequence by key arbitrary async things, like say a database query to load objects by ID.

library code:

/**
 * perform multiple requests in parallel, returning a Promise[Map[Key, Response]]
 * @fBuildUrl function that can build a URL from a key
 * @fAsyncRequest function of one argument that returns a promise
 * @keys the things to load, that fBuildUrl can build a URL from
 *
 * use case: takes a list of componentIDs to load, relative to componentRoot
 * returns a promise to the map of (ComponentID -> componentCfg)
 */
exports.asyncParallel = function (fBuildUrl, fAsyncRequest, keys) {

    var responses = {};
    var asyncLoadOne = function (key) {
        var url = fBuildUrl(key);
        return fAsyncRequest(url).done(function(data) {
            responses[key] = data;
        });
    };

    var promises = _.map(keys, asyncLoadOne);
    return $.when.apply(null, promises).then(function() {
        return responses;
    });
};

application code:

var components = ['tmfFacets', 'tmfHeader', 'tmfDocumentList'];

function buildUrl (key) {
    return componentRoot + "/" + key;
}

util.asyncParallel(buildUrl, util.getJSON, components).done(function(componentConfigs) {
    window.page = new DocListPage(locale, componentConfigs, DocListPageRouter);
});

can we do even better, or is this it?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ util.asyncParallel already returns a promise, you can chain done directly. Using $.when to judge it's return is redundant. \$\endgroup\$
    – Joseph
    Apr 17, 2013 at 18:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.