/** * 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) }); \$\begingroup\$ \$\endgroup\$
Add a comment |
2 Answers
\$\begingroup\$ \$\endgroup\$
0 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) }); \$\begingroup\$ \$\endgroup\$
1 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?
- 1\$\begingroup\$
util.asyncParallelalready returns a promise, you can chaindonedirectly. Using$.whento judge it's return is redundant. \$\endgroup\$Joseph– Joseph2013-04-17 18:53:32 +00:00Commented Apr 17, 2013 at 18:53