functional javascript - how could I generalize this code that correlates parallel async requests with their results? /**
 * takes a list of componentIDs to load, relative to componentRoot
 * returns a promise to the map of (ComponentID -&gt; 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)
});
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)
});