/**
* 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