I have a problem at work: I have a section of installations which are dependent on servers. I want to do this: When a user deletes a server, it loops through installations collection and deletes all dependent installations. For that I use jQuery 'when' function, which is said to wait for a response from the server and then move on to 'then' function. It works flawlessly when there is only one dependent installation. A problem occurs when there are more installations, however, because it moves to the 'then' function immediately after receiving a JSON response.
The question is: How do I make 'when' function wait for all server responses? Eg. I send out three delete requests through $.postJSON and want to move on after I get all three responses. If it's not possible with 'when', what should I use to make it happen? If it helps, I maintain all my entities collections with KnockoutJS. Thanks!
EDIT:
I have it like this:
$.when(DeleteDependentInstallations())
.then (function() {
...
});
DeleteDependentInstallations looks like (pseudocode):
Search the installations collection;
If installation.ID equals server.InstallationID
{
Add to dependent installations collection;
}
Repeat until the whole collection is searched;
for (i = 0; i < dependentInstallations.length; i++)
{
DeleteInstallation(dependentInstallations[i]);
}
DeleteInstallations is a simple function using $.postJSON function.
The problem is the .then function executes immediately after the first JSON response.
I think you need to have DeleteDependentInstallations return an array of JQuery deferreds. $.when allows you to pass multiple arguments to it in order to let it know it has to wait for each one.
I don't know the whole context of what you're doing, but I think something like this might work:
function DeleteDependentInstallations() {
var installations = getDependantInstallations();
var promises = [];
for (var i = 0; i < installations.length; i++) {
var installation = installations[i];
promises.push(DeleteInstallation(installation));
}
return promises;
}
function DeleteInstallation(installation) {
//do whatever here, but return the result of $.ajaxPost
return $.post('/foo', installation);
}
Now when you use that method, it should wait for all returned promises to complete.
$.when.apply(null, DeleteDependentInstallations()).then(function() { alert('wee!'); });
The .apply() is so we can pass an array as an arguments collection.
EDIT: I was confusing "deferreds" and promises in my head. Deferreds are what the $.ajax calls return, and a promise is what the $.when() function returns.
EDIT2: You might also want to look at the .done() method, if the behavior of .then() doesn't suit your needs.
Related
I'm making either 1 or more REST/ajax calls to validate some user information. The rest calls are working well and the information is coming back. The issue I'm facing isn't with that part of the code, which looks something like this.
function ensureUsers(recipients){
var promises = [];
for(var key in recipients){
var payload = {'property':recipients[key]};
promises.push( $.ajax({...}));
}
return $.when.apply($,promises);
}
....
ensureUsers(users) // users is an array of 1 or more users
.done(function(){
console.log(arguments);
)}
If there is more than one user in the initial array, then the arguments in my .done code are structured like this:
[[Object,"success",Object],[Object,"success",Object]...]
I can then iterate over each result, check the status, and proceed.
However if there is only one user in the initial array then .done gets arguments like this:
[Object,"success",Object]
It seems strange to me that the structure of what is returned would change like that. I couldn't find anything about this specific a problem, so I hacked together a solution
var promises = Array.prototype.slice.call(arguments);
if(!Array.isArray(promises[0])){
promises = [promises];
}
Is that really the best I can hope for? Or is there some better way to deal with the returned promises from 1 or more ajax calls in jQuery?
It seems strange to me that the structure of what is returned would change like that.
Yes, jQuery is horribly inconsistent here. When you pass a single argument to $.when, it tries to cast it to a promise, when you pass multiple ones it suddenly tries to wait for all of them and combine their results. Now throw in that jQuery promises can resolve with multiple values (arguments), and add a special case for that.
So there are two solutions I could recommend:
Drop $.when completely and just use Promise.all instead of it:
var promises = [];
for (var p of recipients) {
…
promises.push( $.ajax({…}));
}
Promise.all(promises)
.then(function(results) {
console.log(results);
})
Make each promise resolve with only a single value (unlike $.ajax() that resolves with 3) so that they don't get wrapped in an array, and $.when will produce consistent results regardless of number of arguments:
var promises = [];
for (var p of recipients) {
…
promises.push( $.ajax({…}).then(function(data, textStatus, jqXHR) {
return data;
}) );
}
$.when.apply($, promises)
.then(function() {
console.log(arguments);
})
It appears this functionality is working as currently documented. When you have multiple deferreds passed to $.when it creates a new Promise object that is resolved with the results of each of the results of the passed in deferreds. When you only pass in one, it returns the deferred that you passed in, thus only returning the result instead of an array of results.
I'm not sure if it is any better than your current solution, but you could force it to always have multiple deferreds by having a "fake" deferred that you skip when evaluating the results.
I.E.
function ensureUsers(recipients){
var promises = [$.Deferred().resolve()];
for(var key in recipients){
var payload = {'property':recipients[key]};
promises.push( $.ajax({...}));
}
return $.when.apply($,promises);
}
You could also potentially make it so the placeholder deferred is resolved with the same structure as what you expect in your real results so it would just appear that the first response is always a success.
How do I use $.when in JQuery with chained promises to ensure my ajax requests are completed in the right order?
I have an array called costArray which is made up of a number of dynamic objects. For each item in this array, I'll call an Ajax request called GetWorkOrder which returns a WorkOrder which is basically a table row element with the class .workOrder and appends it to the table with id #tbodyWorkOrders.
Once all the items in the array are processed, I use $.when to let me know when I can calculate the SubTotal of each WorkOrder.
My problem is that my WorkOrders are being inserted in random orders, as the ajax requests are being processed async. How can I ensure my ajax requests are processed and appended in the correct order?
i = 0;
$.each(costArray, function (key, value) {
var d1 = $.get('/WorkOrders/GetWorkOrder', { 'i': i }, function (html) {
$('#tbodyWorkOrders').append(html);
$('.workOrder').last().find('input').val(value.Subtotal);
});
$.when(d1).done(function () {
SetSubtotal();
i++;
});
Edit:
costArray is taken from an earlier ajax call and is an array of items that I am inserting into the table rows:
var costArray = JSON.parse([{"Trade":"Plasterer","Notes":"Test","Subtotal":"3781.00"}]);
The line:
$('.workOrder').last().find('input').val(value.Subtotal);
is one of many that takes the values from GetWorkOrder and puts them into the correct inputs, but I've left out the extra code for clarity
$.when() processes all the promises you pass it in parallel, not sequential (since the async operations have already been started before you even get to $.when()).
It will collect the results for you in the order you pass the promises to $.when(), but there is no guarantee about the execution order of the operations passed to it.
What I would suggest is that you collect all the results (in order), then insert them in order after they are all done.
I've tried to restructure your code, but it is not clear what items form costArray you want to pass to your Ajax call. You weren't passing anything from costArray in your code, but the text of your question says you should be. So, anyway, here's a structural outline for how this could work:
var promises = costArray.map(function (value, index) {
// Fix: you need to pass something from costArray to your ajax call here
return $.get('/WorkOrders/GetWorkOrder', { 'i': value });
});
$.when.apply($, promises).done(function() {
// all ajax calls are done here and are in order in the results array
// due to the wonders of jQuery, the results of the ajax call
// are in arguments[0][0], arguments[1][0], arguments[2][0], etc...
for (var i = 0; i < arguments.length; i++) {
var html = arguments[i][0];
$('#tbodyWorkOrders').append(html);
}
SetSubtotal();
});
Wrap it in a function and recall it from your ajax success:
ajax(0);
function ajax(key) {
$.get('/WorkOrders/GetWorkOrder', {'i' : key },
function (html) {
$('#tbodyWorkOrders').append(html);
$('.workOrder').last().find('input').val(costArray[key].Subtotal);
SetSubtotal();
key++;
if (key < costArray.length)
ajax(key);
});
}
Edit: On further consideration, while this is one way to do it, it entails the ajax calls executing only one at a time, which isn't very time efficient. I'd go with jfreind00's answer.
I have the following scenerio
asyncFunction(x1)
asyncFunction(x2)
asyncFunction(x3)
...
...
asyncFunction(xn)
The function of the asyncFunction is to get data and image from the server and append to the body of the html. The problem is the data sizes vary each time so each function may complete at different time. This makes it useless as some top elements would be still loading even after the bottom elements are loaded.
How can I write the Javascript code in such a way that the functions data is appended after all the callbacks have been completed or alternatively execute the function i only after function j has been executed, where j < i.
I am using Javascript and Ajax for getting the data.
PS: This is almost similar to a news feed.
You can use jquery deffered objects
In every function define deffered object, for example:
var asyncFunction1 = function(x1) {
var d = new $.Deferred();
...
//when it will be ready
d.resolve();
...
return d.promise();
}
And then
$.when(asyncFunction1(), asyncFunction2(), ...).done(function() {
// here all functions will be finished
});
Use can do this easily with using promises. Inform yourself about the topic with articles like this. Then I would suggest you have a look at the Q framework.
What you are looking for would then be that you want to execute code only after all promises have been resolved. This would look like this if you use Q:
Q.all([a,b,c,d]).then(function(){
//runs after all of the promises fulfilled
});
I want to execute a number of http get requests in parallel, map over the results and then resynchronise (join) once all results are ready in order to render the resulting page.
Pseudocode:
var value_needed_to_render_page = async.parallel([array of values to iterate over], function to call in parralel on array).join()
return page_render(value_needed_to_render_page);
I've been looking at async and FutureJS, but didn't work out a good way.
One solution to do that is to use promises. Q.all will expect each of the functions in a list to return a promise, and will wait till all promises resolve. The following example is using q.js, https://github.com/kriskowal/q :
Q.all([
functionToFireRequest1,
functionToFireRequest2,
// (...)
functionToFireRequestN
]).then( function() {
doStuff();
});
Q.all receives a list of functions, so you can also generate the list programmatically.
As an example, the "functionToFireRequest" would look something like this:
function functionToFireRequest1() {
var deferer = Q.defer();
doMyRequestABCFoo( function() { deferer.resolve() } );
// callback should be called inside your request after it finishes
return deferer.promise;
}
So, I have a page that loads and through jquery.get makes several requests to populate drop downs with their values.
$(function() {
LoadCategories($('#Category'));
LoadPositions($('#Position'));
LoadDepartments($('#Department'));
LoadContact();
};
It then calls LoadContact(); Which does another call, and when it returns it populates all the fields on the form. The problem is that often, the dropdowns aren't all populated, and thus, it can't set them to the correct value.
What I need to be able to do, is somehow have LoadContact only execute once the other methods are complete and callbacks done executing.
But, I don't want to have to put a bunch of flags in the end of the drop down population callbacks, that I then check, and have to have a recursive setTimeout call checking, prior to calling LoadContact();
Is there something in jQuery that allows me to say, "Execute this, when all of these are done."?
More Info
I am thinking something along these lines
$().executeAfter(
function () { // When these are done
LoadCategories($('#Category'));
LoadPositions($('#Position'));
LoadDepartments($('#Department'));
},
LoadContact // Do this
);
...it would need to keep track of the ajax calls that happen during the execution of the methods, and when they are all complete, call LoadContact;
If I knew how to intercept ajax that are being made in that function, I could probably write a jQuery extension to do this.
My Solution
;(function($) {
$.fn.executeAfter = function(methods, callback) {
var stack = [];
var trackAjaxSend = function(event, XMLHttpRequest, ajaxOptions) {
var url = ajaxOptions.url;
stack.push(url);
}
var trackAjaxComplete = function(event, XMLHttpRequest, ajaxOptions) {
var url = ajaxOptions.url;
var index = jQuery.inArray(url, stack);
if (index >= 0) {
stack.splice(index, 1);
}
if (stack.length == 0) {
callback();
$this.unbind("ajaxComplete");
}
}
var $this = $(this);
$this.ajaxSend(trackAjaxSend)
$this.ajaxComplete(trackAjaxComplete)
methods();
$this.unbind("ajaxSend");
};
})(jQuery);
This binds to the ajaxSend event while the methods are being called and keeps a list of urls (need a better unique id though) that are called. It then unbinds from ajaxSend so only the requests we care about are tracked. It also binds to ajaxComplete and removes items from the stack as they return. When the stack reaches zero, it executes our callback, and unbinds the ajaxComplete event.
You can use .ajaxStop() like this:
$(function() {
$(document).ajaxStop(function() {
$(this).unbind("ajaxStop"); //prevent running again when other calls finish
LoadContact();
});
LoadCategories($('#Category'));
LoadPositions($('#Position'));
LoadDepartments($('#Department'));
});
This will run when all current requests are finished then unbind itself so it doesn't run if future ajax calls in the page execute. Also, make sure to put it before your ajax calls, so it gets bound early enough, it's more important with .ajaxStart(), but best practice to do it with both.
Expanding on Tom Lianza's answer, $.when() is now a much better way to accomplish this than using .ajaxStop().
The only caveat is that you need to be sure the asynchronous methods you need to wait on return a Deferred object. Luckily jQuery ajax calls already do this by default. So to implement the scenario from the question, the methods that need to be waited on would look something like this:
function LoadCategories(argument){
var deferred = $.ajax({
// ajax setup
}).then(function(response){
// optional callback to handle this response
});
return deferred;
}
Then to call LoadContact() after all three ajax calls have returned and optionally executed their own individual callbacks:
// setting variables to emphasize that the functions must return deferred objects
var deferred1 = LoadCategories($('#Category'));
var deferred2 = LoadPositions($('#Position'));
var deferred3 = LoadDepartments($('#Department'));
$.when(deferred1, deferred2, deferred3).then(LoadContact);
If you're on Jquery 1.5 or later, I suspect the Deferred object is your best bet:
http://api.jquery.com/category/deferred-object/
The helper method, when, is also quite nice:
http://api.jquery.com/jQuery.when/
But, I don't want to have to put a bunch of flags in the end of the drop down population callbacks, that I then check, and have to have a recursive setTimeout call checking, prior to calling LoadContact();
No need for setTimeout. You just check in each callback that all three lists are populated (or better setup a counter, increase it in each callback and wait till it's equal to 3) and then call LoadContact from callback. Seems pretty easy to me.
ajaxStop approach might work to, I'm just not very familiar with it.