问题
I have inherited an app that scans a page looking for data attributes and modifies the dom, adding handlers, etc, accordingly.
I was asked to implement dynamic markup whereby I issue an ajax call to get json data and then use Handlebars to render the new html based on the returned data.
I need to tell the main, outer app to wait until all of the json calls have been resolved before it goes looking for data attributes.
What sort of pattern should I put in place to do this?
Any help would be very much appreciated.
Thanks, Scott
Update
outer.js - scans DOM looking for things to do.
inner.js - issues 0-9 ajax calls (depending on the page I'm on). Each of those ajax call has a .then() function that calls Handlebars functions to write markup.
If I just had a single ajax call, I could pass back inner.js to outer.js as a deferred. Then, in that ajax's .then() function, I'd just call inner.js.resolve().
But if I did this for each of the 9 possible inner.js would resolve after #1 runs, but before #2 finishes.
How do I dynamically set the number of deferred that the outer.js uses to wait until they are ALL resolved?
It doesn't seem possible to late-bind an array like this:
at top of inner.js:
var application = jQuery.Deferred();
var arrayOfAsync = [];
application.done(arrayOfAsync);
Then, when jQuery selectors are found on a page, I could do an
arrayOfAsync.push(localPromise);
But this didn't seem to work... should it? This would assume that the Deferred() is really just holding on to a normal array, which it might not be...
回答1:
You should use promises in this situation.
Let's say you have your handlebars template:
<script id="PostsTempl" type="text/x-handlebars-template">
{{#each posts}}
<li class="item-handlebars">{{body}}</li>
{{/each}}
</script>
which should be filled with a series of posts fetched from a web api/web service.
You will compile the template before using it:
var myTemplate = Handlebars.compile($("#PostsTempl").html());
Now we need a function which will call the web api/web service and will fetche some data:
function fetchData()
{
var deferred = $.Deferred();
$.ajax({
type: 'GET',
dataType: 'json',
url: 'my/api/posts/1/20',
data: {},
success: function (jsonData) {
if (jsonData) {
deferred.resolve(jsonData);
} else {
deferred.reject('');
}
},
error: function (req, status, error) {
var errorMessage = (error.message) ? error.message : error;
deferred.reject(errorMessage);
}
});
return deferred.promise();
}
We will use $.ajax to GET the data. We have defined a promise here:
var deferred = $.Deferred();
which will be resolved when we get the data back:
success: function (jsonData) {
if (jsonData) {
deferred.resolve(jsonData);
} else {
deferred.reject('');
}
},
Or eventually, rejected, if there's no data.
return deferred.promise(); will return our promise.
Now we can call the function which resolves the promise and feeds back some data:
fetchData()
.then(function(data){
// console.log(data);
var posts = {posts: data};
$("#posts").append(myTemplate(posts));
return true;
})
.then(function(result){
goLookForDataAttributes();
})
.fail(function (reason) {
if (reason !== '') {
alert('Something went wrong:' + reason);
}
});
When we get the data back we append the items to our template:
.then(function(data){
// console.log(data);
var posts = {posts: data};
$("#posts").append(myTemplate(posts));
return true;
})
When everything is done we call another function in the other .then() branch:
.then(function(result){
goLookForDataAttributes();
})
Promises can be chained.
the second .then() is called after the first one is executed.
This is the last bit you need:
function goLookForDataAttributes()
{
$('#posts li.item-handlebars').each(function (index, item) {
$(item).addClass('big-font');
});
}
This fiddle might help you.
In this example I parse the posts and add a class when handlebards has rendered the elements.
UPDATE:
Since you're calling a web api/web service, you can execute the promises in parallel, wait until the all the ajax requests have finished and execute your last method.
For the sake of simplicity I am going to create a fake promise (which should be your ajax request):
function buildPromise(id)
{
var deferred = $.Deferred();
setTimeout(function(){
var data = {id: id, name: 'name: ' + id};
deferred.resolve(data);
}, 1000);
return deferred.promise();
}
and I'll create an array of, let's say, 10 promises:
var promises = [];
for (var p = 0; p < 10; p++)
{
promises.push(buildPromise(p));
}
Now I will be able to run all these promises in parallel:
$.when.apply($, promises)
.then(function () {
for(var i = 0; i < arguments.length; i++) {
$('#content').append('<p>' + arguments[i].name + '</p>');
}
}).then(function(results) {
return finalPromise();
})
.then(function(result){
alert('success: ' + result.success);
alert('Finished');
});
$.when.apply($, promises) resolves all the promises together, in parallel, and returns when we get back all the results.
The results can be found in arguments and can be read using the index of the array arguments[x].
When all the ajax requests have been executed we are going to call the finalPromise:
function finalPromise()
{
var deferred = $.Deferred();
setTimeout(function(){
var data = { success: true };
deferred.resolve(data);
}, 1000);
return deferred.promise();
}
finalPromise could be a regular function, with no promises, as well.
This is what it looks like.
Of course now you have to adapt it to your situation.
来源:https://stackoverflow.com/questions/33022854/jquery-asynchronous-programming-pattern